【问题标题】:Declare Doctrine Embeddable as nullable or not声明 Doctrine Embeddable 是否可以为空
【发布时间】:2017-11-21 14:53:10
【问题描述】:

假设我有两个 Doctrine 实体,PersonCompany。两者都有一个接受地址值对象的address 字段。根据业务规则,Company::Address 是必需的,而Person::Address 可以为空。

Doctrine 2.5 提出了the Embeddable type,它显然是在考虑值对象的情况下构建的,事实上,我认为它是我案例的完美解决方案。

但是,我不能做一件事:声明 Person::Address 可以为空,而 Company::Address 则不能。 Embeddable 的字段本身存在一个布尔值nullable 属性,但这当然适用于嵌入地址的每个实体。

是否有人知道我是否遗漏了什么,或者这是由于技术限制,是否有解决方法等?现在我看到的唯一解决方案是将所有可嵌入字段声明为nullable: true 并在我的代码中处理约束。

【问题讨论】:

    标签: php doctrine-orm orm doctrine value-objects


    【解决方案1】:

    有人知道我是否遗漏了什么

    Doctrine 2 不支持 Nullable embeddables。预计它们会升级到版本 3。

    如果有解决方法

    解决方案“是不要在那里使用嵌入,[...] 用嵌入替换字段 [手动]” (@Ocramius)

    例子:

    class Product
    {
        private $sale_price_amount;
        private $sale_price_currency;
    
        public function getSalePrice(): ?SalePrice
        {
            if (is_null($this->sale_price_currency)
                || is_null($this->sale_price_amount)
            ) {
                return null;
            }
    
            return new SalePrice(
                $this->sale_price_currency,
                $this->sale_price_amount
            );
        }
    }
    

    (摘自Harrison Brown

    【讨论】:

    • 友情提示,此方法可以返回null,但声明为SalePrice返回类型。
    【解决方案2】:

    getter 内部的逻辑问题是您无法直接访问属性(如果这样做,您会错过这个特定行为)...

    我试图使用自定义 Hydrator 解决此问题,但问题是当调用 find()、findOneBy()...以及不使用 queryBuilder 的方法时,学说不允许使用自定义 hydrator。

    这是我的解决方案:

    1. 假设我们有一个如下所示的实体:
    <?php
    interface CanBeInitialized
    {
        public function initialize(): void;
    }
    
    class Address
    {
        private $name;
    
        public function name(): string
        {
            return $this->name;
        }
    }
    
    class User implements CanBeInitialized
    {
        private $address;
    
        public function address(): ?Address
        {
            return $this->address;
        }
    
        public function initialize(): void
        {
            $this->initializeAddress();
        }
    
        private function initializeAddress(): void
        {
            $addressNameProperty = (new \ReflectionClass($this->address))->getProperty('value');
    
            $addressNameProperty->setAccessible(true);
    
            $addressName = $addressNameProperty->getValue($this->address);
    
            if ($addressName === null) {
                $this->address = null;
            }
        }
    }
    

    然后你需要创建一个 EventListener 以便在 postLoad 事件中初始化这个实体:

    <?php
    use Doctrine\ORM\Event\LifecycleEventArgs;
    class InitialiseDoctrineEntity
    {
        public function postLoad(LifecycleEventArgs $eventArgs): void
        {
            $entity = $eventArgs->getEntity();
    
            if ($entity instanceof CanBeInitialized) {
                $entity->initialize();
            }
        }
    }
    

    这种方法的好处是我们可以根据我们的需要调整实体(不仅仅是拥有可空嵌入)。例如:在领域驱动设计中,当我们使用六边形架构作为一种战术方法来使用时,我们可以使用我们想要的领域实体所需的所有更改来初始化 Doctrine 实体。

    【讨论】:

    • 恕我直言initialize 逻辑属于EntityListener。话虽如此,我使用这种方法来处理 Embedded/Value Objects 的可空和集合。
    • 我在这里写了一篇关于我的解决方案的文章:medium.com/@wolfgang.klinger/…
    • @Wolfgang 这很有趣,但它仅适用于注释映射。您应该改用真正的学说映射
    • @MassimilianoArione 谢谢,你能详细说明一下(或分享一些链接)吗?我当然不是 Symfony 和 Doctrine 的专家(目前)。 ;)
    • @Wolfgang 正如您在documentation 中所见,映射不限于注释。相反,Doctrine 公开了类(在 Doctrine\ORM\Mapping 命名空间中)来处理 3 种映射形式(注解、xml 和 yaml)
    猜你喜欢
    • 1970-01-01
    • 2011-07-12
    • 1970-01-01
    • 2011-01-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多