将Doctrine Embeddable声明为可空或不可空

13
假设我有两个Doctrine实体,PersonCompany。两者都有一个address字段,该字段接受Address值对象。根据业务规则,Company::Address是必需的,而Person::Address可以为null。
Doctrine2.5提出了可嵌入类型Embeddable type,这显然是专门为值对象设计的,我认为这是我的问题的完美解决方案。
然而,有一件事情我做不到:声明Person::Address可以为空,而Company::Address 不行。可嵌入字段本身存在布尔型nullable属性,但当然适用于嵌入地址的每个实体。
是否有人知道我是否漏掉了什么,或者这是由于技术限制,是否有解决方法等?现在我唯一看到的解决办法是将所有嵌入式字段声明为nullable: true ,并在我的代码中处理约束。
2个回答

12

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

Doctrine 2不支持可为null的嵌入式对象。这些功能预计将在版本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提供的代码片段)


1
友情提示,这个方法可能会返回 null,但是它的返回类型被声明为 SalePrice - Najki

5

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

我尝试使用自定义水合器来解决此问题,但问题是doctrine不允许在调用find()、findOneBy()等方法和不使用queryBuilder的方法时使用自定义水合器。

这是我的解决方案:

  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的集合。 - Cethy
我在这里写了一篇关于我的解决方案的文章:https://medium.com/@wolfgang.klinger/nullable-embeddable-with-symfony-and-doctrine-orm-51f6e2edf623 - Wolfgang
@Wolfgang 这很有趣,但它仅适用于注释映射。你应该使用真正的Doctrine映射。 - Massimiliano Arione
@MassimilianoArione 谢谢,你能详细说明一下吗(或者分享一些链接)?我对Symfony和Doctrine都不是专家。 ;) - Wolfgang
@Wolfgang,正如你可以在文档中看到的那样,映射不仅限于注释。相反,Doctrine公开了处理三种映射形式(注释、xml和yaml)的类(位于Doctrine\ORM\Mapping命名空间中)。 - Massimiliano Arione
如果你想要与框架解耦,这是可怕的。我知道问题中没有明确说明,但如果你有一个领域,它将会受到这种实现的严重污染。 - Alfonso Fernandez-Ocampo

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接