Symfony/Doctrine实体监听器仅首个单元测试触发异常排查
Doctrine postPersist实体监听器触发异常问题
我在使用Doctrine事件监听器时遇到了极为棘手的异常问题,具体场景、配置与现象如下:
业务需求为在以下场景触发时,刷新用户所有已获取权限的汇总数据:
- 用户账号创建完成
- 为用户新增权限
- 为用户关联的用户配置文件新增权限
- 其他实体更新场景(仅上述三类场景即可稳定复现问题)
服务配置
我为对应实体配置了事件监听器,用于派发消息以重新计算权限汇总数据,服务配置如下:
# services.yaml App\EventListener\DroitResume\ListenerOne: tags: - { name: 'doctrine.orm.entity_listener', event: 'postPersist', entity: 'App\Entity\Auth\UserAccount', method: 'postPersistUserAccount', lazy: true} - { name: 'doctrine.orm.entity_listener', event: 'postPersist', entity: 'App\Entity\Auth\UserRight', method: 'postChangeUserRight', lazy: true} - { name: 'doctrine.orm.entity_listener', event: 'postPersist', entity: 'App\Entity\Auth\UserProfileRight', method: 'postChangeUserProfileRight', lazy: true} App\EventListener\DroitResume\ListenerTwo: tags: - { name: 'doctrine.orm.entity_listener', event: 'postPersist', entity: 'App\Entity\Auth\UserAccount', method: 'postPersistUserAccount', lazy: true}
说明:第二个监听器仅用于演示和测试,正式业务代码中仅保留一个监听器
监听器实现代码
// ListenerOne.php class ListenerOne { private MessageBusInterface $messageBus; public function __construct(MessageBusInterface $messageBus) { $this->messageBus = $messageBus; } public function postChangeUserRight(UserRight $userRight): void { print "ListenerOne => dispatch postChangeUserRight\n"; $this->messageBus->dispatch(new UserRefreshResumeRightsMessage($userRight->getUserAccount()->getId())); } public function postChangeUserProfileRight(UserProfileRight $userProfileRight): void { print "ListenerOne => dispatch postChangeUserProfileRight\n"; $this->messageBus->dispatch(new ProfileRefreshResumeRightsMessage($userProfileRight->getUserProfile()->getId())); } public function postPersistUserAccount(UserAccount $userAccount) { print "ListenerOne => dispatch postPersistUserAccount\n"; $this->messageBus->dispatch(new UserRefreshResumeRightsMessage($userAccount->getId())); } }
// ListenerTwo.php class ListenerTwo { private MessageBusInterface $messageBus; public function __construct(MessageBusInterface $messageBus) { $this->messageBus = $messageBus; } public function postPersistUserAccount(UserAccount $userAccount) { print "ListenerTwo => dispatch postPersistUserAccount\n"; $this->messageBus->dispatch(new UserRefreshResumeRightsMessage($userAccount->getId())); } }
单元测试代码
// RefreshRightTest.php class RefreshRightTest extends MyTestCase { //MyTestCase extends KernelTestCase 封装了通用测试方法 public function testPersistUserOne() { $em = $this->getEntityManager(); $user = new UserAccount(); $user->setName('blabla One'); $user->setEmail('blablaone@test.com'); print "Persist User Blabla One\n"; $em->persist($user); $em->flush(); $user = new UserAccount(); $user->setName('blabla Two'); $user->setEmail('blablatwo@test.com'); print "Persist User Blabla Two\n"; $em->persist($user); $em->flush(); } public function testPersistUserRightOne() { $em = $this->getEntityManager(); $user = $this->getExistingUserFromName('Existing User One'); $userRight = new UserRight(); $userRight->setUserAccount($user); $userRight->setRightKey('RIGHT_ONE'); $userRight->setValue(1); print "Persist UserRight One\n"; $em->persist($userRight); $em->flush(); } public function testPersistUserTwo() { $em = $this->getEntityManager(); $user = new UserAccount(); $user->setName('blabla Three'); $user->setEmail('blablathree@test.com'); print "Persist User Blabla Three\n"; $em->persist($user); $em->flush(); } public function testPersistUserRightTwo() { $em = $this->getEntityManager(); $user = $this->getExistingUserFromName('Existing User Two'); $userRight = new UserRight(); $userRight->setUserAccount($user); $userRight->setRightKey('RIGHT_ANY'); $userRight->setValue(2); print "Persist UserRight Two\n"; $em->persist($userRight); $em->flush(); } }
异常现象
测试运行的控制台输出如下:
Persist User Blabla One ListenerOne => dispatch postPersistUserAccount ListenerTwo => dispatch postPersistUserAccount Persist User Blabla Two ListenerOne => dispatch postPersistUserAccount ListenerTwo => dispatch postPersistUserAccount Persist UserRight One ListenerOne => dispatch postChangeUserRight Persist User Blabla Three Persist UserRight Two ListenerOne => dispatch postChangeUserRight
核心异常现象:
- 绑定
UserAccount实体的postPersistUserAccount监听器仅在首个执行的单元测试中会被触发,一旦其他监听器被触发后,该监听器后续永远不会触发 - 绑定
UserRight、UserProfileRight实体的对应监听器可在所有测试中正常触发
已完成排查项
- 所有相关实体上未配置其他任何事件监听器
- 全量代码中不存在任何调用
stopPropagation终止事件传播的逻辑 - 实体间唯一差异为
UserAccount实现了UserInterface接口(作为系统用户表实体)
复现规律
不同测试执行顺序下的复现规律:
顺序1: T1: persist UserAccount T2: persist UserRight T3: persist UserAccount => postPersistUserAccount仅在T1测试中触发 顺序2: T1: persist UserRight T2: persist UserAccount T3: persist UserAccount T4: persist UserRight => postPersistUserAccount全程不触发,postPersistUserRight在T1、T4测试中按预期触发
目前已尝试多种排查方案均未定位问题根因,寻求同类问题的排查思路与解决方案。
内容的提问来源于stack exchange,提问作者Xavier Derrey
相关产品推荐
相关产品推荐

