发布于2026-07-14 阅读(0)
扫一扫,手机访问
软删除这事儿,看着简单,但实际开发中,不少开发者都在这里栽过跟头。ThinkPHP的软删除机制,核心就是“三件套”必须严丝合缝:数据库字段配得对、模型声明写到位、调用姿势得正确。缺了任何一环,你写的delete()就真变成物理删除了,神仙都救不回来。

软删除不是随便加个字段就能用的。我们先得弄明白一件事:字段类型和约束直接决定了逻辑是否生效。在MySQL里,delete_time必须定义为DATETIME NULL或TIMESTAMP NULL。千万别写成NOT NULL DEFAULT CURRENT_TIMESTAMP——否则新记录插入时该字段自动就有值,框架会认为这条数据“已经被删了”,这就尴尬了。
如果你偏好存整型时间戳(比如deleted_at BIGINT UNSIGNED NULL),那模型里必须显式声明类型:protected $type = ['deleted_at' => 'integer'];。不然的话,框架写入null时会被转成字符串,再强转为0,后续查询还是会把它当成已删除的记录。
这里有个容易被忽略的细节:改完字段后,一定要记得手动清空runtime/cache/目录。模型如果读了旧结构的缓存,软删逻辑根本不会触发,查半天都查不出问题。
很多同学以为只要在模型里use think\model\concern\SoftDelete;就够了。还差得远。类定义里必须同时写两样东西:use SoftDelete;和protected $useSoftDelete = true;。缺一不可。
字段名必须与数据库一字不差。数据库字段是deleted_at,你就得写protected $deleteTime = 'deleted_at';。如果用的是布尔型字段is_deleted,那得配成数组形式:protected $deleteTime = ['is_deleted', 1];。
还有一个容易踩的坑:模型如果启用了自动时间戳($autoWriteTimestamp = true),千万别把deleted_at加到$createTime或$updateTime里。否则新增记录时,deleted_at会被自动填充,新数据一入库就被标记为“已删除”,业务逻辑全乱套。
这里需要区分清楚:withTrashed()和onlyTrashed()是查询修饰器,只是改变SQL的WHERE条件,不是恢复指令。调用位置必须在find()或select()之前,写成User::withTrashed()->find(1)才有效。如果写成User::where()->withTrashed()->find(),那就白搭了。
恢复数据的正确姿势分两步:
User::onlyTrashed()->find(123)。注意,这里不能用where()->find(),因为默认查询已经过滤掉了软删数据。$user->restore()。直接写User::restore()或User::where()->restore()都会报错。restore()有个特点:它静默失败。如果返回false而不是抛异常,常见原因不外乎这么几个:字段不允许NULL、类型不匹配、或者记录根本不存在。所以,一定要检查返回值,别指望它自己告诉你出了什么问题。
TP8里没有restoreAll()这个方法。网上抄来的User::restoreAll()写法,一跑就会报错:Call to undefined method。
安全路径有两条:
restoring/restored事件,用User::onlyTrashed()->chunk(500, function ($items) { $items->each->restore(); });。这样逐条恢复,事件能正常触发。Db::name('user')->whereNotNull('delete_time')->update(['delete_time' => null]);。简单粗暴,但事件不会触发。还有一个重要提醒:关联模型不会自动恢复。withTrashed()不透传,restore()不级联。用户恢复了,订单记录仍然软删。你得单独查$user->orders()->onlyTrashed()->select(),再逐条处理。数据量大时,建议用事务包裹起来,避免主从状态不一致。
售后无忧
立即购买>office旗舰店
售后无忧
立即购买>office旗舰店
售后无忧
立即购买>office旗舰店
售后无忧
立即购买>office旗舰店
正版软件
正版软件
正版软件
正版软件
正版软件
1
2
3
7
8