thinkphp5软删除需模型引入softdelete trait、正确定义delete_time字段、仅通过模型方法调用,三者缺一不可;否则delete()或destroy()将直接物理删除。

ThinkPHP5 的软删除不是加个字段或改个配置就能跑通,必须模型引入、字段定义、调用方式三者完全对齐,缺一不可;否则 delete() 或 destroy() 会直接物理删库,查不到数据也找不到原因。
模型里怎么正确引入 SoftDelete trait
很多问题出在“看似引入了,其实没生效”。关键点就三个:
-
use traits\model\SoftDelete;必须写在类顶部(注意是traits\model\SoftDelete,不是think\model\concern\SoftDelete——后者是 TP6 的路径) - 类定义中必须显式写
use SoftDelete;,光导入命名空间不等于启用 trait - 如果软删字段不是默认的
delete_time(比如叫is_deleted或deleted_at),必须声明protected $deleteTime = 'is_deleted'; -
$defaultSoftDelete在 TP5 中仅当用整型标记(如 0/1)时才需配置,且值必须是 0;若用时间戳类型,这个属性不要设,否则可能干扰逻辑
数据库字段必须满足哪些约束
这是静默失败最高发的环节:字段类型或 NULL 约束不对,delete() 表面成功,实际没写入时间、也没报错,数据就“消失”了。
- 字段类型建议用
DATETIME或TIMESTAMP,且必须设为NULL;不能带DEFAULT CURRENT_TIMESTAMP——否则新记录一插入,delete_time就有值,被误判为已删除 - 若用
BIGINT存时间戳(如delete_time BIGINT UNSIGNED NULL),模型里要加protected $type = ['delete_time' => 'integer'];,否则写入时会被转成字符串再强转为 int,结果常为 0 - 绝对禁止设
NOT NULL;哪怕你给它配了默认值 0 或NULL,MySQL 8.0+ 对约束更敏感,仍可能跳过软删逻辑
为什么 Db::table()->delete() 会直接物理删除
软删除是模型层功能,不是数据库层能力。只要调用了 where(),就已脱离模型上下文,进入 Db 类执行流程。
-
UserModel::where('id', 1)->delete()是错的——这本质是Db::name('user')->where()->delete(),绕过了所有模型逻辑 - 正确做法只有两种:
$user->delete()(实例方法)或UserModel::destroy(1)(静态方法) -
destroy()支持批量:UserModel::destroy([1,2,3])、UserModel::destroy('1,2,3'),也支持闭包条件,但底层仍走模型逻辑 - 想物理删,必须显式传
true:UserModel::destroy(1, true)或$user->delete(true)
如何真正物理删除已软删的数据
想物理删除软删数据,必须用 force() 配合 delete() 或 destroy(),不能直接写 SQL 或用 Db::delete()。
-
force()不是独立操作,它只是个链式标记,告诉后续的delete()或destroy()“这次别软删,给我硬删” -
User::onlyTrashed()->where('id', 123)->force()->delete()✅ 正确:先定位软删记录,再强制物理删除 -
User::destroy(123, true)✅ TP5/6 都支持,true参数等价于force() -
User::where('id', 123)->force()->delete()⚠️ 危险:若该记录未软删过,force()仍生效,直接物理删——但你本意可能是恢复或忽略 - 误用
Db::name('user')->where('delete_time', 'not null')->delete()会直接清空回收站,但没走模型事件(deleting/deleted),日志、关联清理全丢失
真正难的不是命令怎么写,而是得时刻记住:force() 必须和模型方法配对使用,且只在明确需要物理删除时才启用——多数场景下,软删数据留着比删掉更安全。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











