You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.19 06:54:59