Doctrine 2 自定义实体加载和持久化

Doctrine 2 custom entity loading and persisting

是否有可能在每个实体的基础上实施 Doctrine 2 中的自定义水合作用和持久性?

Doctrine 2 在值对象(例如集合和 ID)方面有一些主要限制。我想知道是否可以使用自定义机制(或实现)来从对象属性映射到数据库(加载和持久化)。

我知道有一些可能性可以“解决”这个问题,但我喜欢 none 个:

我知道学说中有生命周期事件可能有用。我无法确定 postLoad 事件是否携带一个已经构建的实体对象(包含所有 VO)?因为那样的话对我来说就没用了。

此致, 水母

是的,您可以像这样在 config/packages/doctrine.yaml 中注册新的保湿器:

doctrine:
    dbal: ...
    orm:
        hydrators:
            CustomEntityHydrator: 'App\ORM\Hydrator\CustomEntityHydrator'
            ...
        mapping: ...
        ...

然后您可以像这样在查询中使用它:

public function findCustomEntities(): array
{
    return $this->createQueryBuilder('c')
        ...your query logic...
        ->getResult('CustomEntityHydrator');
}

请注意,您只能指定要用于根实体的水化器。如果您获取关联的实体,您最终可能会得到一个更复杂且难以调试的设置。

相反,您可以考虑仅在实体的界面中处理值对象 (VO)。换句话说,字段是标量值,但您的方法参数和 return 值是 VO。

这是一个实体示例,该实体具有 Uuid 类型的 ID、位置(一些数字标识符)、状态(例如三进制 true/false/null)。这些只是为了展示如何处理不同类型的值对象:

/**
 * @ORM\Entity()
 */
class CustomEntity
{
    /**
     * @ORM\Id()
     * @ORM\Column(type="string", length=64)
     */
    private string $id;

    /**
     * @ORM\Column(type="int")
     */
    private int $location;

    /**
     * @ORM\Column(type="bool, nullable=true)
     */
    private bool $status;

    private function __construct(Uuid $id, Location $location, Status $status)
    {
        $this->id = (string) $id;
        $this->location = $location->getValue();
        $this->status = $status->get();
    }

    public static function new(Location $location, Status $status): self
    {
        return new self(Uuid::v4(), $location, $status);
    }

    public function getId(): Uuid
    {
        return Uuid::fromString($this->id);
    }

    public function getLocation(): Location
    {
        return new Location($this->location);
    }

    public function activate(): void
    {
        $this->status = true;
    }

    public function deactivate(): void
    {
        $this->status = false;
    }

    public function isActive(): bool
    {
        $this->status === true;
    }

    public function isInactive(): bool
    {
        $this->status === false;
    }

    public function isUninitialized(): bool
    {
        $this->status === null;
    }

    public function getStatus(): Status
    {
        if ($this->status === null) {
            return new NullStatus();
        }
        if ($this->status === true) {
            return new ActiveStatus();
        }

        return new InactiveStatus();
    }
}

如您所见,您可以将 new() 替换为 public 构造函数。它与 setter 的工作方式类似。我有时甚至在构造函数中为此使用(私有)设置器。在状态的情况下,如果您使用多种方法在内部设置值,则您甚至不需要设置器。同样,在某些情况下,您可能希望 return 标量值而不是 VO(或者相反,如状态 getter 和 issers 所示)。

重点是,您的实体从外部看起来好像会使用您的 VO,但在内部它已经切换到更适合 Doctrine ORM 的表示。您甚至可以将其与使用 VO 和自定义类型混合使用,例如对于 UUID。你只需要小心,当你的 VO 需要更多的信息来构建而不是你想存储在数据库中时,例如如果我们示例中的数字位置在创建期间也将使用区域设置,那么我们将需要存储它(这很有意义,因为它似乎与数字 ID 相关)或者我们必须在实体中对其进行硬编码或添加抽象上面,它可以访问语言环境,在这种情况下,您的实体可能不会 return Location 或至少不是 LocalizedLocation。

您可能还想考虑不为实体中的每个 属性 设置 VO。虽然它肯定会有帮助,例如将电子邮件包装到自定义 VO 中以确保有效性,而不仅仅是字符串的类型提示,对于像(用户的)名称这样通用的东西,它可能不太有用,它应该对它接受的字符串非常宽容,因为有很多各种各样的名字。使用上面的方法,您可以稍后轻松地引入一个 VO,方法是为 VO 添加一个新的 getter,更改 new() 或任何其他改变您的 属性 的方法,然后不必更改任何内容在下面的数据模型中(除非值的表示方式发生更剧烈的变化)。