tp6.0软删除需显式启用$usesoftdelete=true、统一$deletetime字段名与类型,restore()静默失败须按数据库值、模型配置、关联模型三步排查,批量恢复须分步+事务,关联表需手动处理。

TP6.0 的软删除不是开箱即用的“回收站”,它只管单表 delete_time 字段写入与过滤,关联模型不联动、批量恢复不生效、静默失败不报错——这些坑踩一遍,上线就出事。
模型启用 SoftDelete 必须显式声明 $useSoftDelete
TP6.0+ 虽然支持自动识别 use SoftDelete trait,但若模型中没写 protected $useSoftDelete = true;,destroy() 仍会执行物理删除。
- 低版本(如 TP6.0.0~6.0.7)必须显式设为
true,否则onlyTrashed()查不到数据,restore()也无从触发 - 字段名不匹配会导致整个软删链路失效:默认是
delete_time,若数据库字段叫is_deleted或deleted_at,必须同步声明protected $deleteTime = 'deleted_at'; -
$deleteTime类型必须是时间戳或 NULL;若字段定义为DATETIME NOT NULL DEFAULT '1970-01-01',restore()尝试写入NULL会被 MySQL 拒绝,且 PHP 层不抛异常,只返回false
restore() 静默失败时怎么快速定位?
调用 $user->restore() 返回 false 却没报错,常见原因就三个,按顺序排查:
- 查数据库该行
delete_time是否仍为非空值;如果是,说明写入失败,立刻看 MySQL 错误日志或开启think\facade\Db::enableQueryLog()捕获 SQL - 确认模型是否真启用了软删:
var_dump($user->getOptions()),检查soft_delete是否为true,delete_time字段名是否匹配 - 检查关联模型是否也启用了
SoftDelete并声明了同名$deleteTime;否则$user->orders()->onlyTrashed()->select()查不到任何记录——因为默认查询会自动加delete_time IS NULL条件
批量恢复不能链式调用 onlyTrashed()->where()->restore()
这条代码看着很顺,但完全无效:UserModel::onlyTrashed()->where('status', 1)->restore();。它不会更新任何数据,因为 restore() 是模型实例方法,而链式调用返回的是查询构造器对象,不是模型集合。
- 安全做法分两步:先取 ID 列表,再用
update()直写:$ids = UserModel::onlyTrashed()->where('status', 1)->column('id'); UserModel::whereIn('id', $ids)->update(['delete_time' => null]); - 若需触发
restored事件,必须查出模型实例:UserModel::onlyTrashed()->where('status', 1)->chunk(200, function ($users) { $users->each->restore(); });,且闭包内要判断$user->restore()返回值,跳过失败项 - 批量操作务必放在事务里:
Db::transaction(function () use ($ids) { UserModel::whereIn('id', $ids)->update([...]); });,避免部分成功、部分失败导致状态撕裂
关联模型恢复必须手动逐层处理
$user->restore() 只清 user 表的 delete_time,订单、地址、日志等哪怕也用了 SoftDelete,也不会被连带恢复。
- 不能依赖
$user->orders关系属性——它默认过滤掉软删记录,返回空集合;正确写法是$user->orders()->onlyTrashed()->select() - 量小时可遍历恢复:
$user->orders()->onlyTrashed()->select()->each->restore();;量大时建议绕过模型,用Db::name('order')->where('user_id', $user->id)->whereNotNull('delete_time')->update(['delete_time' => null]),但会跳过事件和验证 - 若在模型
restored事件里自动恢复关联,必须加防循环标记,例如:if (!$this->getAttribute('skip_association_restore')) { $this->setAttribute('skip_association_restore', true)->orders()->onlyTrashed()->update(['delete_time' => null]); }
真正难的不是写 restore(),而是让所有关联表的软删状态同步、事件触发可靠、失败能及时感知——这需要每一层模型都对 $deleteTime 和 $useSoftDelete 保持严格一致,且批量操作永远优先考虑事务边界和底层 SQL 控制力。











