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

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过滤器:

  1. 创建过滤器类:
// 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 []; // 无需暴露过滤参数,自动生效
    }
}
  1. 在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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.27 21:07:48