ThinkPHP 6 的 paginate() 可接收闭包条件,但必须通过 where() 统一传入且确保 COUNT 查询复用该闭包;若闭包逻辑复杂或含 JOIN/子查询,需手动执行 count() 并传 total 参数,否则分页总数易出错。

ThinkPHP 6 的 paginate() 无法直接接收闭包条件?
不是不能,而是写法不对。很多人试图这样写:$model->where(function ($query) { ... })->paginate(),结果发现分页总数(total)算错,或者闭包里的条件在 COUNT 查询里被忽略——这是因为 ThinkPHP 6 默认的分页机制会单独执行一次 COUNT 查询,而该查询默认不复用主查询的闭包逻辑。
根本原因在于:分页时框架会调用 getTotal() 方法生成 COUNT SQL,它默认只保留 where 数组条件,不自动继承闭包式 where。除非你显式告诉它“这个闭包也要进 COUNT”。
- 必须使用
use将外部变量带入闭包,否则闭包内访问不到控制器里的参数(如$keyword) -
paginate()的第二个参数是配置数组,其中query选项可透传 URL 参数,但不影响 WHERE 条件逻辑 - 不要在闭包里调用
order()或limit(),它们对 COUNT 无意义,还可能干扰解析
正确写法:把闭包条件统一塞进 where() 并确保 COUNT 复用
ThinkPHP 6.1+ 支持在 where() 中传入闭包,并且只要不手动改写 getTotal(),后续 paginate() 会自动将该闭包用于 COUNT 查询——前提是闭包没被覆盖或丢弃。
典型安全写法:
立即学习“PHP免费学习笔记(深入)”;
$list = $model->where(function ($query) use ($keyword, $status) {
if ($keyword) {
$query->where('title', 'like', "%{$keyword}%")
->whereOr('content', 'like', "%{$keyword}%");
}
if ($status !== '') {
$query->where('status', $status);
}
})->order('id desc')->paginate([
'list_rows' => 15,
'query' => ['keyword' => $keyword, 'status' => $status],
]);
- 闭包必须作为
where()的唯一参数,不能和其它where()链式调用混用(比如先where('a',1)再where(闭包),后者可能被忽略) -
use中的变量必须真实存在,否则闭包报错,且 PHP 不会提示“变量未定义”,只会让闭包空跑 - 如果用了软删除(
useSoftDelete),闭包条件无需额外加delete_time = null,框架已自动处理
手动控制 COUNT 查询:当闭包逻辑太复杂时
某些场景下,闭包里用了子查询、union 或原生 SQL,ThinkPHP 自动 COUNT 会失败,抛出 SQLSTATE[42000]: Syntax error or access violation。这时得绕过自动 COUNT,自己算总数。
做法是:先构造带闭包的查询对象,手动执行 COUNT,再传给 paginate() 的 total 参数:
$query = $model->where(function ($q) use ($uid) {
$q->where('from_id', $uid)->whereOr('to_id', $uid);
});
$total = $query->count(); // 显式触发 COUNT
$list = $query->order('create_time desc')->paginate([
'list_rows' => 20,
'total' => $total, // 强制指定总数
'page' => input('page/d', 1),
]);
-
count()返回的是 int,不是 Paginator 对象,放心用 - 务必保持
$query对象一致:COUNT 和分页数据查的是同一份where条件 - 如果分页要支持跳转到任意页,记得校验
page是否超过ceil($total / $list_rows),否则最后一页可能空数据
闭包分页 + 关联模型:容易漏掉关联表的条件
当你在闭包里要过滤关联数据(比如“查所有有评论的用户”),别直接在主模型闭包里写 comment.content —— 这会报字段不存在,因为 JOIN 还没发生。
正确方式是:先 with() 或 join(),再在闭包里引用别名:
$list = UserModel::alias('u')
->join('comment c', 'u.id = c.user_id', 'left')
->where(function ($q) use ($keyword) {
$q->where('u.name', 'like', "%{$keyword}%")
->where('c.status', 1); // 注意:这里 c 是 join 别名
})
->group('u.id') // 去重
->order('u.id desc')
->paginate(10);
- 用
alias()给主表起别名,避免和关联表字段冲突 - JOIN 后的 WHERE 必须用别名(如
c.status),不能用comment.status -
group很关键,否则一条用户多条评论会导致分页总数虚高
echo $query->buildSql() 看一眼 COUNT SQL 长什么样,比猜半天快得多。



















