必须在模型类中显式引入think\model\concern\SoftDelete trait并声明$deleteTime,否则软删除方法报错;destroy()为静态批量软删,delete()为实例单条软删,where()->delete()会绕过软删除直接硬删。

模型类里怎么正确引入 SoftDelete Trait
软删除不是开个配置就行,必须在模型类里显式引入 SoftDelete 这个 trait,否则所有软删除方法(比如 onlyTrashed()、restore())都会报错“方法不存在”。
常见错误是只写 use SoftDelete; 却没加命名空间——ThinkPHP6 的路径是 think\model\concern\SoftDelete,不是旧版的 traits\model\SoftDelete。
- 正确写法:
use think\model\concern\SoftDelete;+use SoftDelete; -
protected $deleteTime = 'delete_time';必须声明,字段名要和数据库一致(默认是delete_time,不是deleted_at) - 如果数据库字段类型是
int(时间戳),别忘了设protected $type = ['delete_time' => 'integer'];,否则 NULL 写不进 int 字段会静默失败
destroy() 和 delete() 的行为差异与选法
destroy() 是静态方法,适合批量或条件删除;delete() 是实例方法,必须先查出来再删。两者都走软删除逻辑,但触发时机和参数支持完全不同。
容易踩坑的是:用 where()->delete()(注意不是模型的 delete(),而是查询构造器的)——它绕过模型事件和软删除机制,直接硬删,数据彻底消失。
立即学习“PHP免费学习笔记(深入)”;
- 单条软删:
Users::find(123)->delete();或Users::destroy(123); - 批量软删:
Users::destroy([1, 2, 3]);或Users::destroy(['status' => 0]); - 带条件软删:
Users::destroy(function($query) { $query->where('created_time', ' - 绝对避免:
Users::where('id', 123)->delete();(这是硬删)
查不到被删数据?检查是否漏了 onlyTrashed() 和 withTrashed()
开启软删除后,select()、find() 等所有查询默认自动过滤掉 delete_time IS NOT NULL 的记录。这不是 bug,是设计行为——但新手常以为“删了却还查得到”或“明明删了却查不到”,其实是没搞清查询范围。
- 查正常数据(默认行为):
Users::where('name', '张三')->select(); - 查仅软删除数据:
Users::onlyTrashed()->where('id', 123)->find(); - 查全部(含已软删):
Users::withTrashed()->where('id', 123)->find(); - 恢复数据:
Users::onlyTrashed()->find(123)->restore();(注意:必须先onlyTrashed()才能拿到软删记录)
真正删掉一条软删除记录:force() 不是万能钥匙
force() 是个链式方法,它本身不执行删除,只是告诉后续的 delete() 或 destroy() “这次来真的”。单独调用 $user->force() 没效果,必须接上删除动作。
另一个坑是:用 destroy() 硬删时,第二个参数 true 表示 force,但这个 true 只对主键数组有效,对闭包或条件数组无效——那种情况必须先查再 force()->delete()。
- 正确硬删单条:
$user = Users::onlyTrashed()->find(123); $user->force()->delete(); - 正确硬删主键:
Users::destroy(123, true);或Users::destroy([123, 456], true); - 错误写法:
Users::destroy(function($q) { $q->where('xxx'); }, true);—— 这里的true被忽略
软删除字段一旦被设为非 NULL,就再也无法用普通查询命中,除非你明确切换作用域。这点在写后台回收站或审计日志时,很容易漏掉 withTrashed() 导致数据“凭空消失”。











