Yii框架本身不内置软删除机制,需手动实现:拦截delete()改写为update、重写find()默认过滤is_deleted=0、用updateAll替代deleteAll,并注意关联查询与缓存一致性。

Yii 框架本身不内置软删除机制,必须手动实现——核心是拦截 delete() 行为、改写查询条件、并统一处理字段标记。
软删除字段怎么加才不影响现有查询
添加一个布尔或时间戳字段(如 is_deleted 或 deleted_at)即可,但要注意两点:
- 字段类型建议用
TINYINT(1)(对应 PHPbool)或DATETIME NULL,避免用字符串存 'yes'/'no' - 必须在模型的
find()方法中默认排除已删除记录,否则所有User::find()->all()都会查出脏数据 - 如果已有历史数据,建字段时要设默认值
0或NULL,并批量初始化旧记录
如何重写 find() 让它自动过滤 deleted 状态
在 ActiveRecord 子类中覆盖 find() 静态方法,强制追加未删除条件:
public static function find()
{
return parent::find()->where(['is_deleted' => 0]);
}
这样所有后续调用(如 User::find()->where(...)->one())都会自动带上 is_deleted = 0。但注意:
- 若需查“已删除”的记录(如回收站),得临时绕过:用
parent::find()->where(['is_deleted' => 1]) - 不能只在
beforeFind()中做,因为该方法不适用于静态find()链式调用的初始构建 - 若用
Query对象直接构建(如(new Query())->from('user')),此逻辑不生效——软删除只对模型层有效
delete() 怎么变成 update 而不是真删
重写模型的 delete() 方法,把 SQL DELETE 改成 UPDATE:
public function delete()
{
$this->is_deleted = 1;
return $this->update(['is_deleted']) !== false;
}
关键点:
- 必须返回布尔值,保持与原
delete()接口一致,否则上层逻辑(如if ($model->delete()))会出错 - 不要调用
parent::delete(),否则仍会执行物理删除 - 若字段是
deleted_at,可写成$this->deleted_at = date('Y-m-d H:i:s'); - 该方法不触发
beforeDelete/afterDelete事件,如需审计,应手动发事件或写日志
deleteAll() 这种批量操作怎么兼容软删除
deleteAll() 是静态方法,无法被实例方法拦截,必须单独处理:
- 绝不要直接调用
User::deleteAll($condition),它会跳过模型逻辑,真删数据 - 改用
User::updateAll(['is_deleted' => 1], $condition)替代 - 若条件复杂(含
NOT、IN等),确保格式合规:例如User::updateAll(['is_deleted' => 1], ['AND', ['status' => 'draft'], ['NOT', ['id' => 1]]]) - 事务安全:批量软删建议包裹在
Yii::$app->db->transaction()中,防止部分失败导致状态不一致
软删除最易被忽略的是关联查询和缓存——比如 User::findOne(123) 查不到已软删用户,但如果你用 Redis 缓存了原始 SQL 结果,那个缓存可能还带着旧数据;字段名、索引、迁移回滚逻辑也常被漏掉。这些地方不补全,软删除就只是个半成品。











