简体   繁体   English

Symfony DI:与Doctrine事件订阅者的循环服务引用

[英]Symfony DI : Circular service reference with Doctrine event subscriber

In order to refactor the code about the ticket notification systems, I created a Doctrine listener: 为了重构有关票证通知系统的代码,我创建了一个Doctrine侦听器:

final class TicketNotificationListener implements EventSubscriber
{
    /**
     * @var TicketMailer
     */
    private $mailer;

    /**
     * @var TicketSlackSender
     */
    private $slackSender;

    /**
     * @var NotificationManager
     */
    private $notificationManager;

    /**
     * We must wait the flush to send closing notification in order to
     * be sure to have the latest message of the ticket.
     *
     * @var Ticket[]|ArrayCollection
     */
    private $closedTickets;

    /**
     * @param TicketMailer        $mailer
     * @param TicketSlackSender   $slackSender
     * @param NotificationManager $notificationManager
     */
    public function __construct(TicketMailer $mailer, TicketSlackSender $slackSender, NotificationManager $notificationManager)
    {
        $this->mailer = $mailer;
        $this->slackSender = $slackSender;
        $this->notificationManager = $notificationManager;

        $this->closedTickets = new ArrayCollection();
    }

    // Stuff...
}

The goal is to dispatch notifications when a Ticket or a TicketMessage entity is created or updated trough mail, Slack and internal notification, using Doctrine SQL. 目标是在使用Doctrine SQL通过邮件,Slack和内部通知创建或更新Ticket或TicketMessage实体时发送通知。

I already had a circular dependencies issue with Doctrine, so I injected the entity manager from the event args instead: 我已经与Doctrine有一个循环依赖问题,所以我从事件args中注入了实体管理器:

class NotificationManager
{
    /**
     * Must be set instead of extending the EntityManagerDecorator class to avoid circular dependency.
     *
     * @var EntityManagerInterface
     */
    private $entityManager;

    /**
     * @var NotificationRepository
     */
    private $notificationRepository;

    /**
     * @var RouterInterface
     */
    private $router;

    /**
     * @param RouterInterface $router
     */
    public function __construct(RouterInterface $router)
    {
        $this->router = $router;
    }

    /**
     * @param EntityManagerInterface $entityManager
     */
    public function setEntityManager(EntityManagerInterface $entityManager)
    {
        $this->entityManager = $entityManager;
        $this->notificationRepository = $this->entityManager->getRepository('AppBundle:Notification');
    }

    // Stuff...
}

The manager is injected form the TicketNotificationListener 经理从TicketNotificationListener注入

public function postPersist(LifecycleEventArgs $args)
{
    // Must be lazy set from here to avoid circular dependency.
    $this->notificationManager->setEntityManager($args->getEntityManager());
    $entity = $args->getEntity();
}

The web application is working, but when I try to run a command like doctrine:database:drop for example, I got this: Web应用程序正在运行,但是当我尝试运行像doctrine:database:drop这样的命令时,我得到了这个:

[Symfony\Component\DependencyInjection\Exception\ServiceCircularReferenceException]                                                                                                                                                                                            
  Circular reference detected for service "doctrine.dbal.default_connection", path: "doctrine.dbal.default_connection -> mailer.ticket -> twig -> security.authorization_checker -> security.authentication.manager -> fos_user.user_provider.username_email -> fos_user.user_manager".

But this is concerning vendor services. 但这与供应商服务有关。

How to solve this one? 怎么解决这个? Why I have this error only on cli? 为什么我只在cli上出现此错误?

Thanks. 谢谢。

Had the same architectural problem lately, assuming you use Doctrine 2.4+ the best thing to do is not use the EventSubscriber (which triggers for all events), but use EntityListeners on the two entities you mention. 最近有相同的架构问题,假设您使用Doctrine 2.4+ ,最好的办法是不使用EventSubscriber (触发所有事件),但在您提到的两个实体上使用EntityListeners

Assuming that the behavior of both entities should be the same, you could even create one listener and configure it for both entities. 假设两个实体的行为应该相同,您甚至可以创建一个侦听器并为两个实体配置它。 The annotation looks like this: 注释如下所示:

/** 
* @ORM\Entity()
* @ORM\EntityListeners({"AppBundle\Entity\TicketNotificationListener"})
*/
class TicketMessage

Thereafter you can create the TicketNotificationListener class and let a service definition do the rest: 此后,您可以创建TicketNotificationListener类,并让服务定义执行其余操作:

app.entity.ticket_notification_listener:
    class: AppBundle\Entity\TicketNotificationListener
    calls:
        - [ setDoctrine, ['@doctrine.orm.entity_manager'] ]
        - [ setSlackSender, ['@app.your_slack_sender'] ]
    tags:
        - { name: doctrine.orm.entity_listener }

You might not even need the entity manager here, because the entity itself is available via the postPersist method directly: 您可能甚至不需要实体管理器,因为实体本身可以通过postPersist方法直接使用:

/**
 * @ORM\PostPersist()
 */
public function postPersist($entity, LifecycleEventArgs $event)
{
    $this->slackSender->doSomething($entity);
}

More info on Doctrine entity listeners: http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/events.html#entity-listeners 有关Doctrine实体监听器的更多信息: http//docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/events.html#entity-listeners

IMHO you are mixing 2 different concepts here: 恕我直言,你在这里混合2个不同的概念:

  • Domain Events ( TicketWasClosed for example) 域事件(例如TicketWasClosed
  • Doctrine's Life-cycle Events ( PostPersist for example) 学说的生命周期事件(例如PostPersist

Doctrine's event system is meant to hook into the persistence flow, to deal stuff directly related to saving to and loading from the database. Doctrine的事件系统旨在挂钩持久化流程,处理与保存到数据库和从数据库加载直接相关的内容。 It shouldn't be used for anything else. 它不应该用于任何其他事情。

To me it looks like what you want to happen is: 对我来说,你想要发生的事情是:

When a ticket was closed, send a notification. 关闭故障单后,发送通知。

This has nothing to do with Doctrine or persistence in general. 这与Doctrine或一般的持久性无关。 What you need is another event system dedicated to Domain Events. 您需要的是另一个专门用于域事件的事件系统。

You can still use the EventManager from Doctrine, but make sure you create a second instance which you use for Domain Events. 您仍然可以使用Doctrine中的EventManager ,但请确保创建用于域事件的第二个实例。

You can also use something else. 你也可以用别的东西。 Symfony's EventDispatcher for example. 例如Symfony的EventDispatcher If you're using the Symfony framework, the same thing applies here as well: don't use Symfony's instance, create your own for Domain Events. 如果您正在使用Symfony框架,那么同样适用于此:不要使用Symfony的实例,为Domain Events创建自己的实例。

Personally I like SimpleBus , which uses objects as events instead of a string (with an object as "arguments"). 我个人喜欢SimpleBus ,它使用对象作为事件而不是字符串(对象作为“参数”)。 It also follows the Message Bus and Middleware patterns, which give a lot more options for customization. 它还遵循消息总线和中间件模式,它们提供了更多的自定义选项。

PS: There are a lot of really good articles on Domain Events out there. PS:关于域事件有很多非常好的文章。 Google is your friend :) 谷歌是你的朋友:)

Example

Usually Domain Events are recorded within entities themselves, when performing an action on them. 通常,域事件在对它们执行操作时会记录在实体本身内。 So the Ticket entity would have a method like: 因此Ticket实体将具有如下方法:

public function close()
{
    // insert logic to close ticket here

    $this->record(new TicketWasClosed($this->id));
}

This ensures the entities remain fully responsible for their state and behavior, guarding their invariants. 这可以确保实体对其状态和行为负全部责任,保护其不变量。

Of course we need a way to get the recorded Domain Events out of the entity: 当然,我们需要一种方法将记录的域事件从实体中取出:

/** @return object[] */
public function recordedEvents()
{
    // return recorded events
}

From here we probably want 2 things: 从这里我们可能想要两件事:

  • Collect these events into a single dispatcher/publisher. 将这些事件收集到一个调度程序/发布者中。
  • Only dispatch/publish these events after a successful transaction. 仅在成功交易后发送/发布这些事件。

With the Doctrine ORM you can subscribe a listener to Doctrine's OnFlush event, that will call recordedEvents() on all entities that are flushed (to collect the Domain Events), and PostFlush that can pass those to a dispatcher/publisher (only when successful). 使用Doctrine ORM,您可以订阅Doctrine的OnFlush事件的监听器,该事件将在所有刷新的实体(收集域事件)上调用recordedEvents() ),并将PostFlush到调度程序/发布者(仅在成功时) 。

SimpleBus provides a DoctrineORMBridge that supplies this functionality. SimpleBus提供了一个提供此功能的DoctrineORMBridge

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM