Symfony 7中Doctrine监听器实现软删除失效问题求助
解决软删除实体仍被物理删除的问题
问题根源
你的preRemove监听器仅设置了deletedAt字段,但Doctrine触发preRemove事件时已将实体标记为待删除状态,直接返回无法取消物理删除操作,必须显式告知Doctrine放弃删除并改为更新实体。
修正后的监听器代码
#[AsDoctrineListener(Events::preRemove, priority: 100)] class SoftDeleteEventListener { public function preRemove(PreRemoveEventArgs $args): void { $entityManager = $args->getObjectManager(); $entity = $args->getObject(); // 保留你原有的Trait判断逻辑 $reflection = new \ReflectionClass($entity); if (!in_array(SoftDeleteTrait::class, $reflection->getTraitNames())) { return; } // 设置软删除时间 $entity->setDeletedAt(new \DateTimeImmutable()); // 取消物理删除操作 $unitOfWork = $entityManager->getUnitOfWork(); $unitOfWork->cancelDeletion($entity); // 标记实体为待更新,确保字段变更被持久化 $entityManager->persist($entity); $classMetadata = $entityManager->getClassMetadata(get_class($entity)); $unitOfWork->computeChangeSet($classMetadata, $entity); } }
关键改进点
cancelDeletion方法:Doctrine ORM 2.9+提供的原生方法,直接从待删除队列移除实体,彻底阻止物理删除。- 优先级设置:给监听器设置较高优先级(
priority: 100),确保在其他监听器之前执行,避免被后续逻辑覆盖。 - 显式更新标记:调用
persist和computeChangeSet,强制Doctrine识别实体字段变更,保证deletedAt被写入数据库。
API Platform 统一过滤配置
为让API自动隐藏已软删除的实体,需添加全局Doctrine ORM过滤器:
- 创建过滤器类:
// src/Doctrine/Filter/SoftDeleteFilter.php namespace App\Doctrine\Filter; use ApiPlatform\Doctrine\Orm\Filter\AbstractFilter; use ApiPlatform\Doctrine\Orm\Util\QueryNameGeneratorInterface; use ApiPlatform\Metadata\Operation; use Doctrine\ORM\QueryBuilder; class SoftDeleteFilter extends AbstractFilter { protected function filterProperty(string $property, $value, QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $resourceClass, Operation $operation = null, array $context = []): void { // 自动过滤deletedAt为null的实体 $alias = $queryBuilder->getRootAliases()[0]; $queryBuilder->andWhere(sprintf('%s.deletedAt IS NULL', $alias)); } public function getDescription(string $resourceClass): array { return []; // 无需暴露过滤参数,自动生效 } }
- 在
config/packages/api_platform.yaml启用全局过滤器:
api_platform: doctrine: orm: filters: soft_delete: class: App\Doctrine\Filter\SoftDeleteFilter enabled: true
可选优化:用接口替代Trait判断
为让逻辑更清晰可扩展,建议定义SoftDeletableInterface,让需要软删除的实体实现该接口,替代反射判断:
// src/Entity/Interface/SoftDeletableInterface.php namespace App\Entity\Interface; interface SoftDeletableInterface { public function setDeletedAt(\DateTimeImmutable $deletedAt): void; }
监听器中直接判断:
if (!$entity instanceof SoftDeletableInterface) { return; }
无需依赖反射,代码更简洁且类型安全。
内容的提问来源于stack exchange,提问作者yhu420
相关产品推荐
相关产品推荐

