withTrashed() 必须置于链式调用最前端且不受全局作用域覆盖,否则失效;软删除字段名需与模型 $deleteTime 一致;关联查询需显式调用 withTrashed();paginate() 的 count 查询不继承软删除状态,须手动指定 total。

分页查询时 withTrashed() 不生效?顺序和作用域是关键
直接写 User::withTrashed()->paginate(10) 却只返回正常数据,是因为 withTrashed() 必须放在链式调用最前面,且不能被模型的全局作用域(baseScope())覆盖。
-
withTrashed()本质是移除默认的WHERE delete_time IS NULL条件,但它不改变后续where()或作用域逻辑 - 如果模型重写了
baseScope()并手动加了$query->where('delete_time', null),那withTrashed()就会被二次过滤掉 - 错误写法:
User::where('status', 1)->withTrashed()->paginate(10)——where()在前,withTrashed()已失效 - 正确写法:
User::withTrashed()->where('status', 1)->paginate(10)
软删除字段名不一致导致分页查不到已删数据
ThinkPHP 默认只识别 deleted_at 字段;若你数据库用的是 delete_time,但模型没显式声明 $deleteTime,软删除实际未启用,分页自然不会过滤也不会恢复它。
- 检查模型是否含
protected $deleteTime = 'delete_time';(字段名必须和数据库完全一致) - 确认数据库字段类型是
DATETIME或TIMESTAMP,且允许为NULL;用INT存时间戳会导致whereNotNull('delete_time')判定失败 - 如果字段名是
is_deleted(布尔型),需配合protected $defaultSoftDelete = 0;和类型转换,但此时不能用withTrashed(),得改用where('is_deleted', 1)手动查
关联模型分页 + 软删除:主表能查到,关联表仍是空
比如 Order::withTrashed()->with('user')->paginate(10),订单能显示已软删的,但关联的 user 仍是 null —— 因为 with() 默认不继承主模型的软删除状态。
- 在关联方法里显式加
withTrashed():return $this->belongsTo(User::class)->withTrashed(); - 或用闭包预载:
->with(['user' => function ($q) { $q->withTrashed(); }]) - 若不想让关联也走软删除逻辑,而要“不管删没删都查出来”,可在关联中调用
->removeOption('soft_delete')
分页总数统计被软删除干扰?count() 不自动同步 withTrashed()
paginate() 默认会执行两次 SQL:一次查数据,一次查总数。但第二次 count 查询**不会自动继承 withTrashed() 状态**,导致总数不准(比如显示 12 条,实际只返回 8 条正常数据)。
- 显式传入
total参数绕过自动 count:User::withTrashed()->paginate(10, false, ['total' => User::withTrashed()->count()]) - 更稳妥的做法:用
lengthAwarePaginator手动构造,避免框架隐式行为干扰 - 注意:
onlyTrashed()->paginate()同样存在该问题,总数统计默认仍按“正常数据”算,必须手动补全
分页场景下软删除最易忽略的点,其实是 **count 查询与数据查询的上下文不一致**——它不像单次 select() 那样直接受链式方法影响,而是悄悄走另一套逻辑。不验证总数 SQL,很容易线上出错却查不出原因。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











