软删除恢复(restore())默认不触发 booting/booted,需用 restoring/restored 事件替代;批量恢复不触发任何模型事件,须手动遍历或统一处理。

软删除恢复后不触发 booting 或 booted 怎么办
软删除恢复(restore())默认不会重新走模型的初始化生命周期,booting/booted 不会再次执行,这是 Laravel 的设计行为,不是 bug。如果你依赖这些钩子重置状态、注册监听或初始化属性,得手动补上。
实操建议:
- 在模型中显式定义
restoring和restored事件监听,这两个是软删除专属生命周期钩子 - 避免在
booted()中写“仅首次生效”的逻辑(比如static::$initialized = true),它真就只跑一次 - 若需每次恢复都重置某些字段(如
status、updated_at),直接在restored里做$this->status = 'active'; $this->save();
restored 事件里改字段但没保存到数据库?
常见错误现象:restored 回调里赋值了 $this->is_dirty 为 true,但没调用 save() 或 touch(),导致字段看似变了,实际没入库。
原因在于:Laravel 的事件回调是同步的,但不会自动持久化变更;它和 saving 不同,不处于模型保存事务流中。
使用场景举例:恢复用户时自动把 failed_login_count 归零、重置 last_active_at。
实操建议:
- 在
restored里修改字段后,必须显式调用$this->saveQuietly()(避免触发二次事件) - 不要用
$this->update([...]),它会再次触发updating/updated,可能造成循环或冲突 - 如果只是更新时间戳,用
$this->touch()更轻量
恢复模型后发通知,为什么 Notification::route() 拿不到用户邮箱?
软删除恢复时,模型已从数据库读出,但若你在 restored 事件里立即发通知,而模型的 $casts、$appends 或访问器(accessor)依赖未加载的关系(比如 user.profile),就可能取不到关联数据。
典型错误:在 restored 里写 Notification::send($this, new AccountRestored());,但通知类里 $notifiable->email 是 null —— 因为 email 字段被软删除期间设为 null,或关联 profile 表没 eager load。
实操建议:
- 确认模型的
$fillable和数据库字段一致,软删除字段(deleted_at)不能影响其他字段值 - 发通知前,先用
$this->loadMissing('profile')预加载必要关系 - 更稳妥的做法:把通知逻辑放到队列任务里,用
User::with('profile')->findOrFail($this->id)重新查一次,确保数据完整
批量恢复多个模型,怎么避免 N+1 和事件重复触发
用 User::onlyTrashed()->where(...)->restore() 是批量 SQL 操作,它根本不会实例化模型,因此 restored 事件一个都不会触发 —— 这点极易被忽略。
性能影响很明显:如果误以为写了 restored 就万事大吉,结果通知没发、状态没重置,线上就会出问题。
实操建议:
- 批量恢复必须用集合遍历 + 单个
restore():先get()出模型集合,再each->restore() - 如果数据量大(>100 条),加
withoutEvents()包裹,手动统一处理后续逻辑,避免每个模型都触发全套事件 - 别在循环里发通知,收集 ID 后统一 dispatch 一个队列任务,由任务查出完整数据再发
最常被跳过的细节是:restore() 的批量操作和单条操作行为完全不同,前者绕过所有模型层逻辑,后者才走事件。不验证执行路径,光看代码字面意思,很容易上线后才发现状态没重置、通知全丢了。











