Doctrine软删除过滤器:OnFlush事件删除实体直接消失的原因
这是因为Doctrine的SoftDeleteable扩展的工作机制和OnFlush事件的执行时机不匹配导致的,具体来说:
SoftDeleteable的核心逻辑依赖
preRemove事件
平时调用$em->remove($entity)时,SoftDeleteable会监听preRemove事件,拦截物理删除操作:它会给实体设置deletedAt时间戳,然后把实体的状态从REMOVED改为DIRTY,最终Doctrine只会执行UPDATE语句而非DELETE语句,实现软删除。OnFlush事件的时机导致监听器失效
OnFlush事件是在UnitOfWork(工作单元)已经完成了所有实体状态的初始计算、即将提交事务前触发的。这时候你手动调用$em->remove($entity),SoftDeleteable的preRemove监听器要么没机会触发,要么UnitOfWork不会重新处理这个实体的状态变更,导致物理删除操作直接被执行,实体从数据库中消失。
另外你在OnFlush里才启用softdeleteable过滤器也没用——这个过滤器只是用来在查询时自动过滤已软删除的实体,和软删除的执行逻辑(拦截删除操作)无关。
有两种可靠的处理方式:
方式一:手动模拟软删除逻辑(推荐)
不在OnFlush中调用$em->remove(),而是直接设置deletedAt并标记实体为脏数据:
public function onFlush(OnFlushEventArgs $args) { $em = $args->getEntityManager(); $uow = $em->getUnitOfWork(); foreach ($uow->getScheduledEntityUpdates() as $entity) { if ($entity instanceof Questions) { // 假设你要关联删除Answer实体 $existAnswers = ...; // 你的获取Answer逻辑 foreach ($existAnswers as $answer) { if (!$answer->getDeletedAt()) { $answer->setDeletedAt(new \DateTimeImmutable()); // 告诉UnitOfWork这个实体有变更 $classMetadata = $em->getClassMetadata(get_class($answer)); $uow->recomputeSingleEntityChangeSet($classMetadata, $answer); } } } } }
这种方式绕过了remove操作,直接触发更新,和SoftDeleteable的最终效果一致,还能避免事件时机冲突。
方式二:提前调度删除并触发监听器
如果你坚持要使用$em->remove(),需要手动让UnitOfWork重新处理实体状态,确保SoftDeleteable的监听器能生效:
public function onFlush(OnFlushEventArgs $args) { $em = $args->getEntityManager(); $uow = $em->getUnitOfWork(); foreach ($uow->getScheduledEntityUpdates() as $entity) { if ($entity instanceof Questions) { $existAnswers = ...; foreach ($existAnswers as $answer) { $em->remove($answer); // 手动触发preRemove事件,确保SoftDeleteable拦截 $em->getEventManager()->dispatchEvent( \Doctrine\ORM\Events::preRemove, new \Doctrine\ORM\Event\PreRemoveEventArgs($answer, $em) ); // 重新计算实体的变更集 $classMetadata = $em->getClassMetadata(get_class($answer)); $uow->recomputeSingleEntityChangeSet($classMetadata, $answer); } } } }
不过这种方式不如第一种直接,因为你需要手动触发事件,容易遗漏细节。
- 确保你的
Answer实体已经正确配置了SoftDeleteable注解(比如@Gedmo\SoftDeleteable(fieldName="deletedAt")),并且DoctrineExtensions的监听器已经正确注册到ORM的事件管理器中。 - 尽量避免在
OnFlush中执行实体的增删改操作,因为这个事件的设计目的是让你在提交前检查和调整实体状态,而非执行新的持久化操作,容易引发状态不一致的问题。
内容的提问来源于stack exchange,提问作者shuba.ivan

