ThinkPHP 8.0 的 delete() 默认不删除关联数据,必须显式使用 withDelete() 并满足三个条件:with() 与 withDelete() 关联名一致、关联方法中定义 onDelete('cascade')、子模型也启用软删除;嵌套关联需手动循环处理,且级联操作不自动事务,须手动包裹并捕获 \Throwable。

ThinkPHP 8.0 的 delete() 默认不删关联表
直接调用 User::find(123)->delete() 或 User::destroy(123),只会删 users 表本身。哪怕你已经在模型里写了 hasMany('Order'),子记录一条都不会动——这不是漏写,是框架刻意为之的安全默认。
常见翻车现场:$user->orders->delete() 报错,因为 $user->orders 是 Collection 对象,没有 delete() 方法;而 $user->orders()->delete() 看似可行,但若 orders 表没启用软删除,它会物理删掉所有订单,且不校验外键归属(比如误删了别人家的订单)。
- 必须显式触发关联删除,不能靠“写了关系就自动生效”
-
destroy()静态方法完全不走关联逻辑,哪怕模型定义了onDelete('cascade')也无效 - 软删除开启后,
delete()变成软删,关联模型也跟着软删(前提是它们也启用了SoftDelete)
withDelete() 是最可控的级联入口
想删用户同时清空他的订单和地址,得用 withDelete() 显式声明要连带处理哪些关联:
$user = User::with(['orders', 'addresses'])->find(123); $user->withDelete(['orders', 'addresses'])->delete();
注意三个硬性条件:
立即学习“PHP免费学习笔记(深入)”;
-
with()和withDelete()里的关联名必须完全一致(如'orders',不是'order'或表名'orders_table') - 对应关联方法(如
orders())内部必须有->onDelete('cascade'),写成onDelete(true)会被静默忽略 -
onDelete('cascade')是 ORM 层模拟行为,和数据库外键无关;即使 MySQL 没建FOREIGN KEY ... ON DELETE CASCADE,只要 PHP 层定义了,照样触发
参数可选值只有三个:'cascade'(调子模型 delete())、'null'(设外键字段为 NULL)、'restrict'(阻止主记录删除)。别写错字符串,否则当没写。
嵌套三级关联得手动循环,withDelete 不递归
withDelete(['orders']) 只删到订单层,不会自动删每个订单下的 order_items。想实现 User → Order → OrderItem 的三级清理,必须手动遍历:
$user = User::with('orders')->find(123);
foreach ($user->orders as $order) {
$order->items()->delete();
}
$user->delete();
这里容易踩坑:
-
$user->orders()->items()->delete()会报错,因为$user->orders()返回的是关联查询器,不是模型实例集合,不能链式调items() - 如果
items()关联也定义了onDelete('cascade'),仍需先拿到每个$order实例再调items()->delete(),withDelete不识别嵌套 - 大数据量时,逐条
delete()是 N+1,建议用Db::name('order_items')->where('order_id', 'in', $orderIds)->delete()批量删
事务必须手动加,级联操作不自动包裹
withDelete() 内部所有删除都是独立执行的:主模型删成功、某个子模型删失败,前面的操作不会回滚。必须自己套事务:
Db::transaction(function () use ($user) {
$user->withDelete(['orders', 'addresses'])->delete();
});
但要注意:
-
Db::transaction()只捕获\Exception,不捕获\Error(如 PHP 8.0+ 的TypeError),出错可能静默提交 - 更稳妥写法是手动事务 +
try/catch \Throwable,并在 catch 里显式Db::rollback() - 跨模型操作(如
UserModel::create()+OrderModel::create())可能因连接隔离导致事务失效,统一用Db::name('table')更可靠
真正麻烦的不是语法,而是每层关联是否都定义了正确的 onDelete、外键字段是否被包含、事务边界是否覆盖全部操作——少一个环节,数据就可能残留或不一致。



















