sspat/doctrine-nullable-embeddables

修复 doctrine/orm 以允许使用可空的嵌入对象

v2.16.2 2023-09-08 11:31 UTC

README

当你开始在 Doctrine 实体中使用嵌入值对象时,可能会遇到一个问题,即 Doctrine 会在数据库中相应的值是 null 时实例化值对象。这会导致 PHP 错误。

基本示例

<?php

declare(strict_types=1);

class ValueObject
{
    private $value; // Doctrine will set this property to null on hydration

    public function __construct(string $value)
    {
        $this->value = $value;    
    }

    public function __toString() : string
    {
        return $this->value; // This will be null and throw an error
    }
}

如果你想使用 PHP 7.4 类型属性

<?php

declare(strict_types=1);

class ValueObject
{
    private string $value; // Doctrine will try to set this property to null on hydration and an error will be thrown

    public function __construct(string $value)
    {
        $this->value = $value;    
    }

    public function __toString() : string
    {
        return $this->value;
    }
}

这个问题在 doctrine/orm 存储库的问题中被讨论:doctrine/orm#4568

目前的共识是,这个特性将只出现在 3.x 版本中。

因此,你将有以下选择

使你的值对象属性可空。

这将破坏实体和值对象的不可变性和你的代码中的许多不必要的检查

<?php

declare(strict_types=1);

class ValueObject
{
    private ?string $value;

    public function __construct(string $value)
    {
        if ($value === '') {
            throw new InvalidArgumentException('ValueObject value cannot be an empty string');
        }

        $this->value = $value;    
    }

    public function __toString() : string
    {
        return (string) $this->value; // invariant broken, you will get an empty string     
    }
}

使用 Doctrine 生命周期回调在加湿后重置实体属性为 null。

你仍然需要将值对象的属性设置为可空,以避免加湿期间的错误,你的可空值对象需要一些逻辑,例如实现特殊接口,以允许生命周期回调确定加湿的值是否为 null。

<?php

declare(strict_types=1);

interface NullableValueObjectInterface
{
    public function isNull() : bool;
}

class ValueObject implements NullableValueObjectInterface
{
    private ?string $value;

    public function __construct(string $value)
    {
        $this->value = $value;    
    }

    public function isNull() : bool
    {
        return $this->value === null;
    }

    public function __toString() : string
    {
        return $this->value;
    }
}

你还需要为每个实体配置每个可空值对象,以避免为所有实体属性运行生命周期回调。

<?php

use Doctrine\Common\EventManager;
use Doctrine\ORM\Events;
use Tarifhaus\Doctrine\ORM\NullableEmbeddableListenerFactory;

$listener = NullableEmbeddableListenerFactory::createWithClosureNullator();
$listener->addMapping('App\Domain\User\Model\UserProfile', 'address');

$evm = new EventManager();
$evm->addEventListener([Events::postLoad], $listener);

如果你选择这条路径,有一个实现可用:https://github.com/tarifhaus/doctrine-nullable-embeddable

分叉 Doctrine 以实现你自己的加湿机制

这相当简单,其影响将是维护你自己的分叉并跟上上游更改。

用你自己的覆盖特定的 Doctrine 类

这正是这个包所做的事情。

它将安装特定版本的 doctrine/orm。版本始终与这个包相同,并且被它锁定,所以如果你想更新 Doctrine,你需要将这个包更新到与 Doctrine 版本相同的版本。这种锁定是为了确保不会更改这个包修复的文件。

安装 doctrine/orm 后,将使用 https://github.com/cweagans/composer-patches 对 Doctrine 类 Doctrine/ORM/Mapping/ReflectionEmbeddedProperty 应用补丁。

你可以在 patch/nullable_embeddables.patch 中查看此补丁的内容

它通过分析实体和值对象的属性类型来工作。如果一个包含值对象的实体属性被声明为可空,并且值对象的所有未声明为可空的属性都没有从数据库中获取 null 值 - 则实体属性将被加湿为值对象。所有其他情况都基于提供的类型被认为是无效状态,并将导致在实体的相应属性上加湿为 null。

这种方法的权衡将是

  • 你不能直接更新 Doctrine,只能使用这个包更新它
  • 你需要 PHP 7.4
  • 包含可空值对象的实体属性必须是类型化的
  • 包含可空值对象的实体属性必须是可空的
  • 可空值对象的属性必须是类型化的

一个工作示例如下所示

<?php

declare(strict_types=1);

class Entity
{
    private ?ValueObject $nullableValueObject;

    public function setValueObject(string $value) : void
    {
        $this->nullableValueObject = new ValueObject($value);
    }

    public function getValueObject() : ?ValueObject
    {
        return $this->nullableValueObject;
    }
}

class ValueObject
{
    private string $value;

    public function __construct(string $value)
    {
        $this->value = $value;    
    }

    public function __toString() : string
    {
        return $this->value;    
    }
}

如果你选择这条路径,你可以按照以下步骤安装此包

  • 添加到你的 composer.json
{
    "extra": {
        "enable-patching": true
    }
}

然后根据您想使用的 doctrine/orm 版本

  • 运行 composer require sspat/doctrine-nullable-embeddables:v2.16.2 doctrine/orm:2.16.2
  • 运行 composer require sspat/doctrine-nullable-embeddables:v2.16.1 doctrine/orm:2.16.1
  • 运行 composer require sspat/doctrine-nullable-embeddables:v2.16.0 doctrine/orm:2.16.0
  • 运行 composer require sspat/doctrine-nullable-embeddables:v2.15.5 doctrine/orm:2.15.5
  • 运行 composer require sspat/doctrine-nullable-embeddables:v2.15.4 doctrine/orm:2.15.4
  • 运行 composer require sspat/doctrine-nullable-embeddables:v2.15.3 doctrine/orm:2.15.3
  • 运行 composer require sspat/doctrine-nullable-embeddables:v2.15.2 doctrine/orm:2.15.2
  • 运行 composer require sspat/doctrine-nullable-embeddables:v2.15.1 doctrine/orm:2.15.1
  • 运行 composer require sspat/doctrine-nullable-embeddables:v2.15.0 doctrine/orm:2.15.0
  • 运行 composer require sspat/doctrine-nullable-embeddables:v2.14.3 doctrine/orm:2.14.3
  • 运行 composer require sspat/doctrine-nullable-embeddables:v2.14.2 doctrine/orm:2.14.2
  • 运行 composer require sspat/doctrine-nullable-embeddables:v2.14.1 doctrine/orm:2.14.1
  • 运行 composer require sspat/doctrine-nullable-embeddables:v2.14.0 doctrine/orm:2.14.0
  • 运行 composer require sspat/doctrine-nullable-embeddables:v2.13.5 doctrine/orm:2.13.5
  • 运行 composer require sspat/doctrine-nullable-embeddables:v2.13.4 doctrine/orm:2.13.4
  • 运行 composer require sspat/doctrine-nullable-embeddables:v2.13.3 doctrine/orm:2.13.3
  • 运行 composer require sspat/doctrine-nullable-embeddables:v2.13.2 doctrine/orm:2.13.2
  • 运行 composer require sspat/doctrine-nullable-embeddables:v2.13.1 doctrine/orm:2.13.1
  • 运行 composer require sspat/doctrine-nullable-embeddables:v2.13.0 doctrine/orm:2.13.0
  • 运行 composer require sspat/doctrine-nullable-embeddables:v2.12.4 doctrine/orm:2.12.4
  • 运行 composer require sspat/doctrine-nullable-embeddables:v2.12.3 doctrine/orm:2.12.3
  • 运行 composer require sspat/doctrine-nullable-embeddables:v2.12.2 doctrine/orm:2.12.2
  • 运行 composer require sspat/doctrine-nullable-embeddables:v2.12.1 doctrine/orm:2.12.1
  • 运行 composer require sspat/doctrine-nullable-embeddables:v2.12.0 doctrine/orm:2.12.0
  • 运行 composer require sspat/doctrine-nullable-embeddables:v2.11.3 doctrine/orm:2.11.3
  • 运行 composer require sspat/doctrine-nullable-embeddables:v2.11.2 doctrine/orm:2.11.2
  • 运行 composer require sspat/doctrine-nullable-embeddables:v2.11.1 doctrine/orm:2.11.1
  • 运行 composer require sspat/doctrine-nullable-embeddables:v2.11.0 doctrine/orm:2.11.0
  • 运行 composer require sspat/doctrine-nullable-embeddables:v2.10.5 doctrine/orm:2.10.5
  • 运行 composer require sspat/doctrine-nullable-embeddables:v2.10.4 doctrine/orm:2.10.4
  • 运行 composer require sspat/doctrine-nullable-embeddables:v2.10.3 doctrine/orm:2.10.3
  • 运行 composer require sspat/doctrine-nullable-embeddables:v2.10.2 doctrine/orm:2.10.2
  • 运行 composer require sspat/doctrine-nullable-embeddables:v2.10.1 doctrine/orm:2.10.1
  • 运行 composer require sspat/doctrine-nullable-embeddables:v2.10.0 doctrine/orm:2.10.0
  • 运行 composer require sspat/doctrine-nullable-embeddables:v2.9.6 doctrine/orm:2.9.6
  • 运行 composer require sspat/doctrine-nullable-embeddables:v2.9.5 doctrine/orm:2.9.5
  • 运行 composer require sspat/doctrine-nullable-embeddables:v2.9.4 doctrine/orm:2.9.4
  • 运行 composer require sspat/doctrine-nullable-embeddables:v2.9.3 doctrine/orm:2.9.3
  • 运行 composer require sspat/doctrine-nullable-embeddables:v2.9.2 doctrine/orm:2.9.2
  • 运行 composer require sspat/doctrine-nullable-embeddables:v2.9.1 doctrine/orm:2.9.1
  • 运行 composer require sspat/doctrine-nullable-embeddables:v2.9.0 doctrine/orm:2.9.0
  • 运行 composer require sspat/doctrine-nullable-embeddables:v2.8.5 doctrine/orm:2.8.5
  • 运行 composer require sspat/doctrine-nullable-embeddables:v2.8.4 doctrine/orm:2.8.4
  • 运行 composer require sspat/doctrine-nullable-embeddables:v2.8.3 doctrine/orm:2.8.3
  • 运行 composer require sspat/doctrine-nullable-embeddables:v2.8.2 doctrine/orm:2.8.2
  • 运行 composer require sspat/doctrine-nullable-embeddables:v2.8.1 doctrine/orm:2.8.1
  • 运行 composer require sspat/doctrine-nullable-embeddables:v2.8.0 doctrine/orm:2.8.0
  • 运行 composer require sspat/doctrine-nullable-embeddables:v2.7.5 doctrine/orm:2.7.5
  • 运行 composer require sspat/doctrine-nullable-embeddables:v2.7.4 doctrine/orm:2.7.4
  • 运行 composer require sspat/doctrine-nullable-embeddables:v2.7.3 doctrine/orm:2.7.3
  • 运行 composer require sspat/doctrine-nullable-embeddables:v2.7.2 doctrine/orm:2.7.2
  • 运行 composer require sspat/doctrine-nullable-embeddables:v2.7.1 doctrine/orm:2.7.1
  • 运行 composer require sspat/doctrine-nullable-embeddables:v2.7.0 doctrine/orm:2.7.0