[英]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个不同的概念:
TicketWasClosed
for example) TicketWasClosed
) 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: 从这里我们可能想要两件事:
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.