Symfony中Order状态变更创建通知时Doctrine ORM preUpdate事件无限执行原因
我的Symfony项目包含Order和Notification两个实体,需求是当Order的status字段变更时自动创建通知。我实现了Doctrine\Bundle\DoctrineBundle\EventSubscriber\EventSubscriberInterface的OrderEventSubscriber,订阅了Doctrine\ORM\Events::preUpdate事件。但调用flush保存通知时,preUpdate事件开始无限执行,尽管已经做了Order实例检查。
相关代码
OrderEventSubscriber的preUpdate方法
public function preUpdate(PreUpdateEventArgs $args) { $entity = $args->getObject(); if ($entity instanceof Order) { $onlyStatusChanged = count($args->getEntityChangeSet()) === 1 && $args->hasChangedField('status'); if ($onlyStatusChanged) { $this->notificationsCreator->createChangeStatusNotification($entity->getCustomer()); } } }
通知创建方法
public function createChangeStatusNotification(User $recipient) { $notification = new Notification(); $notification->setAction((new DataMapping())->getKeyByValue('notification_actions', 'order_status_changed')); $notification->setRecipient($recipient); $notification->setCreatedAt(new DateTimeImmutable('now', new DateTimeZone('Europe/Moscow'))); $this->em->persist($notification); $this->em->flush(); }
问题原因
在preUpdate事件中调用$em->flush()会触发完整的实体生命周期流程,哪怕只保存Notification,Doctrine依然会检查所有被管理的实体(包括正在更新的Order),导致preUpdate事件被反复触发,形成无限循环。
解决办法
方案1:改用postUpdate事件
将订阅的事件从preUpdate改为Doctrine\ORM\Events::postUpdate,该事件在Order实体完成更新后触发,此时调用flush不会再触发Order的preUpdate事件:
// 修改OrderEventSubscriber的getSubscribedEvents方法 public function getSubscribedEvents(): array { return [ Events::postUpdate, ]; } // 对应的处理方法名改为postUpdate public function postUpdate(PostUpdateEventArgs $args) { $entity = $args->getObject(); if ($entity instanceof Order) { $changeSet = $args->getEntityChangeSet(); if (isset($changeSet['status'])) { $this->notificationsCreator->createChangeStatusNotification($entity->getCustomer()); } } }
方案2:flush时指定仅保存Notification
如果必须使用preUpdate事件,可以在flush时只传入Notification实例,避免Doctrine检查所有实体:
// 修改createChangeStatusNotification方法 public function createChangeStatusNotification(User $recipient) { $notification = new Notification(); $notification->setAction((new DataMapping())->getKeyByValue('notification_actions', 'order_status_changed')); $notification->setRecipient($recipient); $notification->setCreatedAt(new DateTimeImmutable('now', new DateTimeZone('Europe/Moscow'))); $this->em->persist($notification); $this->em->flush($notification); // 仅刷新当前Notification实例 }
方案3:延迟到事务提交后执行
把通知创建逻辑放到事务提交之后,避免在实体更新过程中触发额外的flush:
可以利用Symfony的KernelEvents::TERMINATE事件,或者Doctrine的postFlush事件,在事务完全完成后再创建并保存通知。
内容的提问来源于stack exchange,提问作者Shamil Hairulaev

