ThinkPHP软删除需模型配置、数据库字段、调用方式三者严格对齐:数据库加NULLable的delete_time字段;模型引入SoftDelete trait并配置$deleteTime;删除必须用模型方法而非Db类;查询默认过滤已删数据,可用onlyTrashed()或withTrashed()控制范围。

ThinkPHP模型软删除不是“一开即用”的开关功能,它必须模型配置、数据库字段、调用方式三者严格对齐,缺一不可。任一环节出错,delete()可能静默变成真删,或查询完全漏掉本该出现的数据。
第一步:数据库加字段,类型和约束必须精准
在对应数据表中添加软删除标记字段,推荐命名为 delete_time(TP6 默认强绑定此名,不建议改):
- MySQL 中建为
DATETIME NULL DEFAULT NULL—— 绝对不能设NOT NULL,也不能配DEFAULT CURRENT_TIMESTAMP,否则新记录一插入就带时间,被误判为“已删除” - 若用整数时间戳(如
BIGINT),字段也必须允许NULL,且后续要在模型中声明类型转换 - 修改表结构后,务必清空
runtime/cache/目录,否则模型可能缓存旧结构导致逻辑失效
第二步:模型里正确引入并启用 SoftDelete
仅导入命名空间不够,必须显式启用 trait 并配置关键属性:
- 顶部导入:
use think\model\concern\SoftDelete;(TP6 路径,不是 TP5 的traits\model\SoftDelete) - 类内声明:
use SoftDelete; - 显式指定字段:
protected $deleteTime = 'delete_time';—— 即使是默认名也建议写上,避免继承时被覆盖 - 若字段是整型时间戳,补上类型声明:
protected $type = ['delete_time' => 'integer']; - 如果模型同时启用了自动时间戳(
$autoWriteTimestamp = true),切勿把delete_time加入$createTime或$updateTime,否则新增时就写入时间,刚入库即“被删”
第三步:删除操作必须走模型方法,禁用 Db 类直删
软删除是模型层拦截机制,Db 类操作完全绕过:
立即学习“PHP免费学习笔记(深入)”;
- ✅ 正确(触发软删):
UserModel::destroy(123)、$user->delete()、UserModel::destroy([1,2,3]) - ❌ 错误(直接真删):
Db::name('user')->delete(123)、UserModel::where('id', 123)->delete()—— 这本质是 Db 查询器调用,不经过模型逻辑 - 要物理删除,必须显式传
true:UserModel::destroy(123, true)或$user->delete(true)
第四步:查询行为默认过滤,需主动控制范围
启用后,所有 select()、find()、get() 都自动加 AND delete_time IS NULL:
- 查正常数据(默认):
UserModel::where('status', 1)->select() - 查仅软删数据(回收站):
UserModel::onlyTrashed()->select() - 查全部(含已删):
UserModel::withTrashed()->select()—— 注意必须放在链最前面,如withTrashed()->where(...)->select() - 关联查询(如
with('orders'))不会继承主模型状态,子模型要单独加:with(['orders' => function($q) { $q->withTrashed(); }])



















