删除前校验必须在 beforedelete() 中实现,因其是 activerecord 唯一安全可控的钩子;控制器中直接判断后调用 delete() 会绕过生命周期,导致关联清理、日志、软删除等逻辑失效。

删除前校验必须在 beforeDelete() 里写
Yii 的 ActiveRecord 删除流程中,beforeDelete() 是唯一安全、可控的钩子点。直接在控制器里 if ($model->status !== 'active') 判断再调用 delete() 是错的——它绕过了模型自身的生命周期,后续事件、日志、软删除逻辑全失效。
常见错误现象:删掉一条记录后发现关联数据没清理、审计日志没写、或软删除字段没更新,就是因为校验逻辑没进 beforeDelete()。
- 方法必须是
public,返回bool;返回false会中止删除,且不抛异常 - 不能依赖
$this->getErrors()或validate()结果——beforeDelete()不自动触发验证,得手动加 - 若需复用已有规则(比如检查是否被引用),建议封装成独立方法,避免重复写 SQL
关联数据存在时禁止删除,用 exists() 避免 N+1
要阻止删除已被订单引用的用户,别写 User::findOne($id)->orders 再 count,这会触发额外查询甚至全表加载。直接用 exists() 做轻量探测。
示例:
public function beforeDelete()
{
if (Order::find()->where(['user_id' => $this->id])->exists()) {
$this->addError('id', '该用户存在未完成订单,不可删除');
return false;
}
return parent::beforeDelete();
}
-
exists()只查SELECT 1,性能远优于count()或all() - 若关联表有复合条件(如只拦「status = pending」的订单),把条件直接塞进
where,别等查出来再 PHP 过滤 - 注意事务:如果删除和关联检查跨库或需强一致性,把整个操作包进
Transaction
软删除场景下,校验逻辑要区分物理删和逻辑删
用了 is_delete 字段做软删除后,delete() 默认走的是逻辑删,但业务上可能仍需要“彻底清除测试账号”这类物理删权限。这时校验不能一刀切。
- 先判断当前调用的是不是物理删:
if (!$this->isAttributeSafe('is_delete')) { /* 物理删路径 */ } - 逻辑删允许部分字段为空(如清空
phone),物理删则可能要求audit_log_id必填——得在beforeDelete()里按路径分支处理 - 别在
rules()里给is_delete加required:它不是表单字段,是内部状态
自定义错误提示要进 errors,不能只 throw Exception
很多人写 throw new BadRequestHttpException('禁止删除'),这会让前端收到 400 状态但无具体字段错误,API 客户端难定位问题。正确做法是调用 $this->addError(),让错误和字段绑定。
-
addError('id', 'xxx')→ 错误归到id字段,API 返回{"id": ["xxx"]} - 想加全局错误?用
addError('', 'xxx'),空字符串键对应模型级错误 - 不要在
beforeDelete()里调validate()后又手动 addError:重复加会导致错误数组里出现两条相同信息
实际删操作永远发生在 beforeDelete() 返回 true 之后,而这个函数本身没有默认校验行为——所有判断都得你亲手写进去,漏掉任何分支,就等于放行了不该删的数据。











