thinkphp 的 restore() 触发 restoring(恢复前,返回 false 可中断)和 restored(恢复后,仅当 delete_time 确实置为 null 时触发);需在模型中配置事件或绑定监听器;restoring 中可校验权限或业务规则并返回 false 阻止非法恢复;restored 中宜用 saveall 批量恢复关联数据;所有操作应包裹在 db::transaction 中确保事务一致性。

ThinkPHP 本身没有“事件驱动的数据恢复”机制,所谓“事件做数据恢复”,其实是把模型事件(如 restoring、restored)和软删除恢复逻辑配合使用,用来在恢复前后插入校验、日志、关联处理等动作——它不替代恢复本身,只是增强恢复过程的可控性。
restore() 触发哪些模型事件
调用 $model->restore() 时,ThinkPHP 会按顺序触发:
-
restoring:恢复前触发,可在此中断恢复(返回false) -
restored:恢复成功后触发,仅当数据库字段真实更新为NULL才触发
注意:restoring 和 restored 是模型实例事件,不是静态方法或全局钩子;必须在模型类中定义 protected $event = [...] 或使用 observe() 绑定监听器。
怎么在 restoring 中阻止非法恢复
比如只允许管理员恢复、或禁止恢复已过期的记录,可在 restoring 回调里加判断:
public function restoring()
{
if (!auth('admin')->check()) {
return false; // 中断恢复
}
if ($this->delete_time && strtotime($this->delete_time) $this->id]);
return false;
}
}
关键点:
- 返回
false会直接终止本次restore(),且不报错,只静默失败 -
$this->delete_time是原始字段值,不是 PHP 时间戳,需手动转换 - 事件里不能用
throw new Exception(),否则整个事务会崩,且restore()返回false而非异常
restored 事件里怎么安全处理关联数据
软删除默认不级联,但你可以在 restored 里主动拉取并恢复关联项:
public function restored()
{
// 恢复订单下的所有订单项
$this->items()->onlyTrashed()->chunk(100, function ($items) {
foreach ($items as $item) {
$item->delete_time = null;
}
OrderItem::saveAll($items);
});
}
注意:
- 别在
restored里调$this->items()->restore()—— 关联模型没启用SoftDeletetrait 的话会报错 - 用
saveAll()批量更新比循环调restore()更高效,也避免触发重复事件 - 如果关联模型也有
restored事件,它们会各自触发,无需额外注册
事件 + 事务恢复时最容易漏掉的一件事
模型事件默认不在事务内自动回滚。比如你在 restored 里写了一条日志到另一张表,但主恢复因数据库约束失败了,那条日志仍会留在库里。
正确做法是:手动开启事务,并确保事件里的所有操作都共享同一连接:
Db::transaction(function () use ($user) {
if (false === $user->restore()) {
throw new Exception('Restore failed');
}
// 日志、关联更新等都在同一事务里
});
否则,哪怕 restore() 成功,后续事件里的 DB 操作失败,也不会回滚主记录的恢复状态——这是线上恢复出错后数据不一致的常见根源。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











