为了重构票证通知系统的代码,我创建了一个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...
}
目标是在Ticket或TicketMessage实体通过邮件、Slack和内部通知创建或更新时,使用Doctrine SQL分发通知。
我已经有了Doctrine的循环依赖问题,所以我从事件参数中注入了实体管理器:
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...
}
从TicketNotificationListener
注入管理器
public function postPersist(LifecycleEventArgs $args)
{
// Must be lazy set from here to avoid circular dependency.
$this->notificationManager->setEntityManager($args->getEntityManager());
$entity = $args->getEntity();
}
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".
但是这是关于供应商服务的。
如何解决这个问题?为什么我只有在cli上才有这个错误?
谢谢。
最近遇到了同样的架构问题,假设您使用Doctrine 2.4+
,最好不要使用EventSubscriber
(它触发所有事件),而是对您提到的两个实体使用EntityListeners
。
假设两个实体的行为应该相同,您甚至可以创建一个侦听器并为两个实体配置它。注释看起来像这样:
/**
* @ORM'Entity()
* @ORM'EntityListeners({"AppBundle'Entity'TicketNotificationListener"})
*/
class TicketMessage
之后,您可以创建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 }
您可能在这里甚至不需要实体管理器,因为实体本身可以通过postPersist
方法直接获得:
/**
* @ORM'PostPersist()
*/
public function postPersist($entity, LifecycleEventArgs $event)
{
$this->slackSender->doSomething($entity);
}
关于Doctrine实体监听器的更多信息:http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/events.html#entity-listeners
恕我直言,你在这里混合了两个不同的概念:
- 域事件(以
TicketWasClosed
为例) - 原则生命周期事件(以
PostPersist
为例)
对我来说,你想要发生的事情是:
当票被关闭时,发送通知。
这与一般的原则或持久性无关。你需要的是另一个专门用于域事件的事件系统。
你仍然可以使用Doctrine中的EventManager,但要确保你创建了一个用于Domain Events的第二个实例。
你也可以用别的东西。例如Symfony的EventDispatcher。如果你正在使用Symfony框架,同样的事情也适用于这里:不要使用Symfony的实例,为Domain Events创建你自己的实例。
我个人喜欢SimpleBus,它使用对象作为事件而不是字符串(用对象作为"参数")。它还遵循消息总线和中间件模式,这些模式为自定义提供了更多选项。
PS:有很多关于领域事件的好文章。谷歌是你的朋友:)
通常,在对实体执行操作时,域事件被记录在实体本身中。因此,Ticket
实体将具有如下方法:
public function close()
{
// insert logic to close ticket here
$this->record(new TicketWasClosed($this->id));
}
这确保实体对它们的状态和行为完全负责,保护它们的不变量。
当然,我们需要一种方法从实体中获取记录的域事件:
/** @return object[] */
public function recordedEvents()
{
// return recorded events
}
从这里我们可能需要两个东西:
- 将这些事件收集到单个dispatcher/publisher中。
- 仅在事务成功后才调度/发布这些事件。
使用Doctrine ORM,你可以订阅一个监听器到Doctrine的OnFlush
事件,它将在所有刷新的实体上调用recordedEvents()
(收集域事件),PostFlush
可以将这些传递给调度程序/发布者(只有在成功时)。
SimpleBus提供了一个提供此功能的DoctrineORMBridge。