微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

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

如何解决Doctrine 2 自定义实体加载和持久化

是否可以基于每个实体在 Doctrine 2 中实现自定义的水化和持久化?

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

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

  • 虚假实体需要在实体中进行适当处理,这会将持久层泄漏到域对象中
  • 真正的实体需要在持久性方面做更多工作(更多存储库和更复杂的处理)
  • Embaddables 具有上述限制
  • 具有序列化功能自定义 DBAL 类型使查询某些值变得不可能或至少非常慢

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

最好的问候, 棘状体

解决方法

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

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

然后您可以在查询中使用它,如下所示:

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

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

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

这是一个示例,该实体具有 Uuid 类型的 id、位置(某些数字标识符)、状态(例如三元真/假/空)。这些只是为了展示如何处理不同类型的值对象:

/**
 * @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() 替换为公共构造函数。它与二传手类似。我有时甚至在构造函数中为此使用(私有)setter。如果您使用多种方法在内部设置值,则在状态的情况下,您甚至不需要设置器。同样,在某些情况下,您可能希望返回标量值而不是 VO(或者相反,如状态 getter 和 issers 所示)。

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

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

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。