软删除恢复前必须手动检查业务状态,因为restore()仅清空deleted_at且不校验,需显式判断如订单状态或退款记录,并封装为canBeRestored()方法;直写SQL会绕过所有校验与事件;恢复后须refresh()同步状态;唯一索引冲突时restore()可能静默失败,须检查返回值并捕获异常。

软删除恢复前必须手动检查业务状态
Laravel 的 restore() 方法本身不触发任何校验逻辑,它只是把 deleted_at 设为 null 并保存。业务上是否允许恢复(比如订单已发货、用户已退费、关联数据已被清理),得你自己写判断,框架不会替你拦。
常见错误现象:restore() 执行成功,但业务出错——比如恢复了一个已被财务结算的退款单,导致账务重复;或者恢复了被下游系统标记为“永久失效”的用户,引发权限混乱。
- 必须在调用
restore()前,显式检查关键字段或关联状态,例如:$order->status !== 'refunded'、!$user->hasActiveRefundRecord() - 不要依赖模型事件(如
restoring)做阻断:事件里抛异常虽能中断,但此时数据库事务可能已部分提交,且逻辑分散难追踪 - 推荐把校验封装成模型方法,比如
canBeRestored(),返回布尔值,便于复用和测试
用 Query Builder 而非 Eloquent 直接 restore 会跳过所有校验
如果你用 DB::table('users')->where('id', 123)->update(['deleted_at' => null]) 或 User::withTrashed()->where('id', 123)->update(['deleted_at' => null]),不仅绕过 canBeRestored(),连 restoring 和 restored 事件都不会触发。
使用场景:批量恢复、后台脚本、迁移任务——这些地方最容易因图快而直写 SQL,结果漏掉业务约束。
- 除非明确知道无业务影响,否则一律走 Eloquent 实例的
restore() - 批量恢复时,别用
whereIn+update,改用循环 + 单条restore(),并在循环内调用校验 - 如果性能真成瓶颈(比如几千条),可先用查询预筛出可恢复 ID 列表,再分批走 Eloquent 恢复
软删除字段被修改后,restore() 不会自动刷新模型状态
调用 $model->restore() 后,$model->deleted_at 在 PHP 对象里仍是旧值(比如一个时间戳),哪怕数据库已更新为 null。后续代码若直接读这个属性,会误判为“仍被软删除”。
性能影响小但逻辑隐患大:比如你在 restore() 后紧接着写 if ($model->trashed()) { ... },结果进错分支。
- 恢复后立刻调用
$model->refresh(),确保内存状态与数据库一致 - 或者改用
$model->fresh()重新查一次(更稳妥,尤其涉及关联或计算属性时) - 别依赖
$model->isForceDeleting()或$model->wasRecentlyCreated等内部标志来推断恢复结果
软删除 + 唯一索引冲突时 restore() 会静默失败
如果表里有带 deleted_at 的唯一联合索引(比如 UNIQUE KEY `unique_email_deleted` (`email`, `deleted_at`)),恢复时可能因 email 已被其他未删除记录占用而报 SQLSTATE[23000]: Integrity constraint violation,但 Laravel 默认不抛异常,而是返回 false。
容易踩的坑:前端显示“恢复成功”,实际数据库没变,日志也没记录,问题拖到下游才暴露。
- 始终检查
restore()返回值:if (!$user->restore()) { /* 处理失败 */ } - 捕获
QueryException,并特别判断错误码是否为23000,再给出具体提示(如“邮箱已被其他活跃用户使用”) - 开发期在数据库加这类索引后,务必补全恢复路径的冲突测试用例
事情说清了就结束。软删除不是开关,是状态机;恢复不是回滚,是业务决策。校验点藏在模型里、事务外、日志下——漏一个,线上就多一个深夜告警。


















