裸调 destroy 危险因无权限校验、无软删除、可被任意用户滥用;安全做法是授权+软删除+事务包裹,用 $model->delete() 触发策略和事件,禁用 model::destroy() 和原生 sql 删除。

直接用 destroy 方法删数据,不加权限校验和软删除控制,就是最常见也是最危险的写法。
为什么不能裸调 destroy?
裸调 destroy 意味着:只要路由能被访问(比如 CSRF 绕过、Token 泄露、ID 猜解),任意用户都能删掉任意记录。它不检查当前用户有没有权限、目标记录是否属于该用户、甚至不区分物理删除还是软删除。
常见错误现象包括:
- 普通用户通过修改 URL 中的
$id删除他人订单 - 前端没做权限隐藏,非管理员也能看到并点击“删除”按钮
- 误删后无法恢复,因为用了
delete()而非softDelete
必须加的三道防线:授权 + 软删除 + 事务包裹
安全删除不是加个中间件就完事,得在控制器里逐层落实:
- 用
authorize方法触发策略(如PostPolicy::delete()),而不是只靠中间件拦截路由 - 模型开启软删除:在模型中加
use SoftDeletes;,并在迁移中添加$table->softDeletes(); - 物理删除必须走显式确认流程,且用
DB::transaction包裹关联清理逻辑(比如删文章时同步删评论) - 避免用
Model::destroy($id)—— 它绕过模型事件和策略,应改用$model->delete()或$model->forceDelete()
destroy 方法里怎么写才不算埋雷?
一个干净、可审计的 destroy 应该长这样:
public function destroy(Request $request, Post $post)
{
// 1. 策略授权(触发 PostPolicy::delete)
$this->authorize('delete', $post);
// 2. 软删除(触发 deleted 事件,支持恢复)
$post->delete();
// 3. 可选:记录操作日志
AuditLog::create([
'user_id' => $request->user()->id,
'action' => 'post_deleted',
'subject_id' => $post->id,
'subject_type' => Post::class,
]);
return response()->noContent();
}
注意点:
-
Post $post是隐式路由模型绑定,它自动做了findOrFail,但不会自动检查权限 —— 所以仍需authorize - 不要在
destroy里手动写DB::table('posts')->where(...)->delete(),那等于废掉所有 Eloquent 安全机制 - 如果真要硬删(比如 GDPR 数据擦除),必须用
$post->forceDelete(),且该操作应单独路由、独立日志、带二次确认
容易被忽略的坑:软删除 + 关联级联
软删除本身不级联。比如你删了一个 User,它的 posts() 不会自动软删除 —— 除非你手动处理。常见疏漏:
- 没重写模型的
deleting事件,导致子记录残留为“孤儿” - 用
withTrashed()查询时忘了加条件,把已删数据混进列表 - 在 API 响应里直接返回
$post->deleted_at字段,暴露删除状态给前端
真正安全的级联软删除,得靠模型观察者或显式事务:
在 User 模型里监听 deleting,然后遍历并调用 $this->posts()->each->delete();或者在控制器里用 DB::transaction 批量软删主从记录。











