Doctrine MongoDB preUpdate事件无法持久化HistoryAsset文档问题
问题描述
在Doctrine MongoDB ODM的EventSubscriber中,为Asset实体记录历史版本时遇到异常:
prePersist处理器可正常将HistoryAsset文档持久化到数据库preUpdate处理器代码已执行(通过dump确认HistoryAsset1被标记为待插入),但HistoryAsset文档并未写入数据库,且无任何报错
正常工作的prePersist代码
public function prePersist(LifecycleEventArgs $eventArgs) { if ($eventArgs->getDocument() instanceof Asset) { $historyAsset = new HistoryAsset(); $historyAsset->setAction('CREATE'); $historyAsset->setData($this->normalizer->normalize($eventArgs->getObject(),null, [ AbstractNormalizer::CALLBACKS => [ 'typ' => function(AssetTyp $typ) { return $typ->getName(); }, ], ])); $historyAsset->setAsset($eventArgs->getDocument()); $this->documentManager->persist($historyAsset); } }
无法工作的preUpdate代码
public function preUpdate(LifecycleEventArgs $eventArgs): void { if ($eventArgs->getDocument() instanceof Asset) { //$changeset = $eventArgs->getDocumentManager()->getUnitOfWork()->getDocumentChangeSet($eventArgs->getDocument()); $historyAsset1 = new HistoryAsset(); $historyAsset1->setAction('UPDATE'); $historyAsset1->setData([]); //$historyAsset->setData($this->normalizer->normalize($changeset)); //$historyAsset->setAsset($eventArgs->getDocument()); $this->documentManager->persist($historyAsset1); //dump($this->documentManager->getUnitOfWork()->isScheduledForInsert($historyAsset1)); // true //dump($historyAsset1); // correct } }
HistoryAsset文档定义
#[Document(collection: 'History')] class HistoryAsset { #[Id(strategy: 'AUTO')] protected string $id; #[ReferenceOne(storeAs: "id", targetDocument: Asset::class)] protected Asset|null $asset = null; #[Field] protected string $action = ''; #[Field] protected array $data = []; public function __construct() { } public function getId(): int { return $this->id; } public function getAction(): string { return $this->action; } public function setAction(string $action): void { $this->action = $action; } public function getData(): array { return $this->data; } public function setData(array $data): void { $this->data = $data; } public function getAsset(): Asset|null { return $this->asset; } public function setAsset(Asset $asset): void { $this->asset = $asset; } }
排查与解决方案
1. 核心原因
Doctrine ODM的preUpdate事件触发时,UnitOfWork已完成对Asset实体的变更集计算,此时新persist的HistoryAsset虽被标记为待插入,但默认flush流程不会包含它——因为flush操作基于触发事件的原始变更队列执行。
2. 修复方案
方案一:手动调度文档插入
在persist后,调用UnitOfWork的scheduleInsert方法,强制将HistoryAsset加入插入队列:
public function preUpdate(LifecycleEventArgs $eventArgs): void { if ($eventArgs->getDocument() instanceof Asset) { $dm = $eventArgs->getDocumentManager(); $historyAsset1 = new HistoryAsset(); $historyAsset1->setAction('UPDATE'); $historyAsset1->setData([]); $historyAsset1->setAsset($eventArgs->getDocument()); // 建议恢复该行,关联目标Asset $dm->persist($historyAsset1); // 手动调度插入操作 $uow = $dm->getUnitOfWork(); $uow->scheduleInsert($historyAsset1); } }
方案二:改用postUpdate事件
postUpdate在Asset实体更新完成后触发,此时flush流程可正常处理新persist的文档:
public function postUpdate(LifecycleEventArgs $eventArgs): void { if ($eventArgs->getDocument() instanceof Asset) { $dm = $eventArgs->getDocumentManager(); $historyAsset1 = new HistoryAsset(); $historyAsset1->setAction('UPDATE'); // 若需记录修改内容,可获取变更集 $changeset = $dm->getUnitOfWork()->getDocumentChangeSet($eventArgs->getDocument()); $historyAsset1->setData($this->normalizer->normalize($changeset)); $historyAsset1->setAsset($eventArgs->getDocument()); $dm->persist($historyAsset1); } }
3. 额外排查点
- 修正
HistoryAsset的ID类型:$id定义为string但getId()返回int,类型不匹配会导致持久化失败,改为public function getId(): string - 确认EventSubscriber已正确注册
preUpdate事件:检查getSubscribedEvents方法是否包含Events::preUpdate - 开启Doctrine日志:查看是否有插入
History集合的请求被发送但未执行成功
内容的提问来源于stack exchange,提问作者JeromeT
相关产品推荐
相关产品推荐

