软删除需同时配置filter启用、实体类注解及字段映射三者缺一不可;仅装扩展或加注解无效,必须在doctrine.yaml启用filter、stof配置开启softdeleteable、类上加@Gedmo\SoftDeleteable并匹配nullable datetime字段。

软删除不是加个注解就自动生效,必须同时配置 filter 启用 + 实体类注解 + 字段映射,三者缺一不可。
doctrine.orm.filters 必须显式启用 soft-deleteable
只装 stof/doctrine-extensions-bundle 或只写 @Gedmo\SoftDeleteable() 注解,完全不会触发软删逻辑。Doctrine 默认不拦截任何查询,得靠 filter 主动过滤。
-
config/packages/doctrine.yaml中必须注册并启用 filter:doctrine: orm: filters: soft-deleteable: class: Gedmo\SoftDeleteable\Filter\SoftDeleteableFilter enabled: true -
config/packages/stof_doctrine_extensions.yaml中必须开启扩展:stof_doctrine_extensions: softdeleteable: true
- 启用后,所有
find*、findBy、DQL 查询默认跳过deletedAt IS NOT NULL的记录——你甚至不用写 WHERE 条件
@Gedmo\SoftDeleteable() 必须写在实体类上,且 fieldName 要匹配 datetime 字段
把注解贴在字段上(比如 private $deletedAt; 上方)是无效的,Doctrine 完全忽略。
- 正确位置是类声明上方:
/** * @ORM\Entity * @Gedmo\SoftDeleteable(fieldName="deletedAt", timeAware=true, hardDelete=false) */ class Article
-
fieldName="deletedAt"必须对应一个已声明的@ORM\Column(type="datetime", nullable=true)字段 -
hardDelete=false是关键:它让$em->remove($entity)只执行 UPDATE,不发 DELETE SQL -
timeAware=true会让 Doctrine 自动设new \DateTime();设为false则需手动赋值
CollectionType 表单里的子实体不会自动软删
父实体加了软删除,子实体(如 @OneToMany 关联)仍会走硬删逻辑,除非你单独处理。
- 每个子实体类自己也得加
@Gedmo\SoftDeleteable()注解 - 关联映射中避免用
cascade={"remove"},改用cascade={"persist"} - 表单提交时,
allow_delete: true触发的$collection->removeElement($child)不会自动设$child->setDeletedAt() - 必须在
PreRemove事件监听器里或表单处理后手动调用$child->setDeletedAt(new \DateTimeImmutable())
绕过软删除查/删数据,禁用 filter 不够,还得清 EntityManager 缓存
想查已软删记录,或真删某条归档数据,光调 $em->getFilters()->disable('soft-deleteable') 很可能查不到——因为缓存里还存着旧的 filtered 查询结果。
- 必须配对操作:
$em->getFilters()->disable('soft-deleteable'); $em->clear(); - 物理删除某条记录时,可临时设
hardDelete=true(仅限该实例),或直接执行原生 SQL - 如果用 API Platform,统一软删逻辑建议用
StateProcessorInterface替代监听器,更可控
最容易被忽略的是:子实体软删必须独立配置,且表单移除动作不会自动触发软删逻辑——这个坑在复杂嵌套关系里几乎必踩。


















