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

Doctrine2:实体的有界上下文和 SINGLE_TABLE 继承映射有点困惑我是否以正确的方式做这件事

如何解决Doctrine2:实体的有界上下文和 SINGLE_TABLE 继承映射有点困惑我是否以正确的方式做这件事

长话短说。

我使用 Doctrine 的单表继承映射来映射一个公共实体的三个不同上下文(类):NotActivatedCustomerDeletedCustomerCustomer.此外,还有一个 AbstractCustomer 包含以下内容

App\Identity\Domain\Customer\AbstractCustomer:
  type: entity
  inheritanceType: SINGLE_TABLE
  discriminatorColumn:
    name: discr
    type: string
  discriminatorMap:
    Customer: App\Identity\Domain\Customer\Customer
    NotActivatedCustomer: App\Identity\Domain\Customer\NotActivatedCustomer
    DeletedCustomer: App\Identity\Domain\Customer\DeletedCustomer
  table: customer
  id:
    id:
      type: customer_id
      unique: true
      generator:
        strategy: CUSTOM
      customIdGenerator:
        class: Symfony\Bridge\Doctrine\IdGenerator\UuidV4Generator
  fields:
    email:
      type: email
      length: 180
      unique: true

子类型定义示例:

<?PHP

declare(strict_types=1);

namespace App\Identity\Domain\Customer;

use App\Identity\Domain\User\Email;

class DeletedCustomer extends AbstractCustomer
{
    public const TYPE = 'DeletedCustomer';

    public function __construct(CustomerId $id)
    {
        $this->_setId($id);
        $this->_setEmail(new Email(sprintf('%s@mail.local',$id->value())));
    }
}

用例:

<?PHP

declare(strict_types=1);

namespace App\Identity\Application\Customer\UseCase\DeleteCustomer;

use App\Identity\Application\Customer\CustomerEntityManager;
use App\Identity\Application\User\AuthenticatedCustomer;
use App\Identity\Domain\Customer\DeletedCustomer;
use App\Shared\Application\ImageManager;

final class DeleteCustomerHandler
{
    private CustomerEntityManager $customerEntityManager;
    private AuthenticatedCustomer $authenticatedCustomer;
    private ImageManager $imageManager;

    public function __construct(AuthenticatedCustomer $authenticatedCustomer,CustomerEntityManager $customerEntityManagerByActiveTenant,ImageManager $customerPhotoManager)
    {
        $this->customerEntityManager = $customerEntityManagerByActiveTenant;
        $this->authenticatedCustomer = $authenticatedCustomer;
        $this->imageManager = $customerPhotoManager;
    }

    public function handle(): void
    {
        $customer = $this->authenticatedCustomer->customer();

        $photo = (string) $customer->photo();

        $deletedCustomer = new DeletedCustomer($customer->id());

        // Todo OR return DeletedCustomer that way
        // $deletedCustomer = $customer->deactive();

        //  entityManager->merge() called here
        $this->customerEntityManager->sync($deletedCustomer);

        // simple entityManager->flush() under the hood
        $this->customerEntityManager->update();

        // that's a raw query to update discriminator field,hackish way I'm using
        // UPDATE customer SET discr = ? WHERE id = ?
        $this->customerEntityManager->updateInheritanceType($customer,DeletedCustomer::TYPE);

        if ($photo) {
            $this->imageManager->remove($photo);
        }
    }
}

因此,如果您已经有一个现有的 Customer 持久化并运行 DeleteCustomerHandlerCustomer 将被更新,但它的鉴别器字段不会! 谷歌搜索,没有办法更新鉴别器字段,而不是像我那样采用某种黑客方式(手动运行原始查询来更新字段)。

此外,我需要使用 EntityManager->merge() 方法将手动初始化的 DeletedCustomer 添加到内部 UnitOfWork。看起来也有点脏,而且它是 Doctrine 3 不推荐使用的方法,所以问题还有没有更好的方法来处理我的情况?

所以,总结所有问题:

  1. 我是否将 Customer's 状态更改为 DeletedCustomer 完全错误?我只是想避免客户上帝对象,区分这个实体的有界上下文,有点那样。
  2. 如何避免 EntityManager->merge() 出现? AuthenticatedCustomer 来自会话 (JWT)。

解决方法

我认为您希望避免客户变成上帝对象是绝对正确的。继承是一种方法,但将其用于不同状态的客户可能会导致问题。

我的经验中的两个关键问题:

  1. 随着新状态的出现,您会继续添加不同的继承实体吗?
  2. 当您让一位客户经历两种不同的状态时会发生什么,例如一位曾是 NotActivatedCustomer 但现在是 DeletedCustomer 的客户?

因此,只有当继承的类型真正是更具体的类型时,我才会保留继承,其中给定的实体在其整个生命周期中永远只是这些类型之一。例如,汽车不会变成摩托车。

我有两种模式来解决与您不同的问题。我倾向于从第一个开始,然后转向第二个。

interface DeletedCustomer
{
  public function getDeletedAt(): DateTime;
}

interface NotActivatedCustomer
{
  public function getCreatedAt(): DateTime;
}

class Customer implements DeletedCustomer,NotActivatedCustomer
{
  private $id;
  private $name;
  private DateTime $deletedAt;
  private bool $isActivated = false;

  public function getDeletedAt(): DateTime {...}
  public function getCreatedAt(): DateTime {...}
}

class DeletedCustomerRepository
{
  public function findAll(): array
  {
    return $this->createQuery(<<<DQL
      SELECT customer 
      FROM   Customer 
      WHERE  customer.deletedAt IS NOT NULL
    >>>)->getQuery()->getResults();
  }
}

class NotActivatedCustomerRepository
{
  public function findAll(): array
  {
    return $this->createQuery(<<<DQL
      SELECT customer 
      FROM   Customer 
      WHERE  customer.isActivated = false
    >>>)->getQuery()->getResults();
  }
}

class DeletedCustomerService
{
  public function doTheThing(DeletedCustomer $customer) {}
}

这减少了耦合,这是神对象的主要问题之一。因此,当列开始激增时,我可以将它们移到加入 Customer 的真实实体中。引用 DeletedCustomer 的组件仍会收到一个。

第二种模式是 event-sourcing-lite - 与“CustomerLifecycleEvent”实体具有多对一的关系。根据客户是否有“已删除”事件进行查询。第二种方法要复杂得多,无论是更新还是查询。您仍然可以拥有返回实体(如 DeletedCustomer)的专用存储库,但您需要做更多的样板工作。

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