thinkphp 8.0 不支持 join 软删除,必须分步手动实现:先模型层主表软删,再显式子表软删,最后校验一致性并确保字段类型与配置严格匹配。

ThinkPHP 8.0 不支持 JOIN 语法直接软删除,所谓“多表联合软删除”本质上不是一条 SQL 完成的原子操作,而是需按依赖关系、业务语义分步控制的逻辑策略。核心原则是:**软删除只在模型层生效,且仅对当前模型表字段(如 delete_time)做标记更新;关联表必须显式、独立处理,不能依赖自动级联**。
一、先明确哪些操作根本不可行
以下写法在 TP8 中会直接报错或静默失效,务必规避:
-
Db::name('user')->join('order', 'user.id = order.user_id')->where('user.status', 0)->delete()→ 报语法错误(JOIN + delete()已被底层 Query 类移除) -
UserModel::with('orders')->delete(1)→with()不参与删除逻辑,orders 表完全不受影响 -
OrderModel::where('user_id', $uid)->delete()(未走模型软删配置)→ 物理删除,跳过所有软删机制 - 在模型中定义
$deleteTime = ['is_deleted', 1]却用DATETIME类型字段 → 类型不匹配导致标记失败,无报错但数据“消失”
二、正确实现多表软删除的三步逻辑链
以「删除用户同时软删其全部订单」为例,需手动构建完整闭环:
-
步骤 1:主表软删除(触发起点)
必须通过模型调用:UserModel::destroy($uid)或$user->delete()。确保模型已启用SoftDelete,字段delete_time为DATETIME NULL,且未被纳入自动时间戳字段列表。 -
步骤 2:子表软删除(显式逐表处理)
不能靠 JOIN,改用子查询 + 模型 update:OrderModel::where('user_id', $uid)->update(['delete_time' => date('Y-m-d H:i:s')])。若 OrderModel 也启用了 SoftDelete,可改用OrderModel::where('user_id', $uid)->delete()(此时才是软删)。 -
步骤 3:状态一致性校验(防漏删/错删)
执行后建议补查:OrderModel::onlyTrashed()->where('user_id', $uid)->count(),确认数量匹配。若需事务保障,应包裹在Db::transaction()中,并捕获异常回滚。
三、关联查询时展示“联合软删”效果
前端常需展示「用户 + 其所有订单(含已软删)」,这需两层 withTrashed:
- 主模型解除过滤:
UserModel::withTrashed()->find($uid) - 关联模型也解除过滤:
UserModel::with(['orders' => function ($q) { $q->withTrashed(); }])->find($uid) - 若订单表本身未启用 SoftDelete,
withTrashed()无效,此时应统一用布尔字段(如is_deleted TINYINT(1) DEFAULT 0),并在模型中配protected $deleteTime = ['is_deleted', 1];
四、字段与类型必须严丝合缝
任何一环出错都会导致软删“看似成功实则无效”:
- 数据库字段必须为
delete_time DATETIME NULL DEFAULT NULL(禁用NOT NULL DEFAULT CURRENT_TIMESTAMP) - 若用
BIGINT存时间戳,模型中必须声明:protected $type = ['delete_time' => 'integer']; - 修改字段后,务必清空
runtime/cache/目录,否则模型仍读缓存旧结构 - 字段名非默认
delete_time?必须在模型中精确声明:protected $deleteTime = 'deleted_at';,大小写、下划线须完全一致
不复杂但容易忽略
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











