ThinkPHP分页必须由paginate()接管原始查询链,不能在select()/all()结果上调用;需显式传入false禁用自动解析以支持自定义参数;render()前不可调用toArray()等方法;复杂查询下total()可能不准,应手动传入总数。

ThinkPHP 分页不是调用一个方法就自动生效,关键在于 paginate() 必须接管原始查询链,否则会报 Call to undefined method think\Collection::paginate() 或返回空数据。
paginate() 必须链式调用,不能在数组或集合上调用
常见错误是先 select() 或 all() 拿到全部数据,再试图分页:
-
User::where('status', 1)->select()->paginate(10)❌ —— 这是在 PHP 数组上分页,没走数据库 LIMIT,性能差且总数不准 -
User::all()->paginate(10)❌ ——all()返回think\Collection,它没有paginate()方法 -
User::where('status', 1)->paginate(10)✅ —— 查询构造器未执行,paginate()才能插入 COUNT + LIMIT 逻辑
手动控制 page 和 list_rows 时别漏掉 false 参数
默认情况下 paginate(10) 会自动从 $_GET['page'] 读页码、但不会读每页条数;如果你希望支持 ?page=2&size=20,必须显式关闭自动解析:
- 漏掉
false:User::paginate(input('size/d', 15), true, ['page' => input('page/d', 1)])→page被解析两次,翻页跳转错乱 - 正确写法:
$size = input('size/d', 15); User::paginate($size, false, ['page' => input('page/d', 1)]) - 如果用
query透传搜索参数(如 keyword),也要一并塞进第三个参数里:['query' => ['keyword' => $keyword], 'page' => ...]
render() 前千万别调用 toArray() 或 json()
$users->render() 依赖分页对象的完整上下文(含总条数、URL 配置、当前页等)。一旦你提前做了以下任一操作,render() 就失效:
立即学习“PHP免费学习笔记(深入)”;
-
$users->toArray()→ 返回纯数组,丢失分页元信息 -
json($users)或return $users(API 场景)→ 对象被序列化,render()方法不可再调用 -
foreach ($users as $u) { ... }后再render()→ 行为未定义,多数情况静默失败
API 场景建议单独提取分页字段:['data' => $users->items(), 'total' => $users->total(), 'per_page' => $users->listRows(), 'current_page' => $users->currentPage()]。
total() 是缓存值,复杂查询下可能不准
$users->count() 返回的是当前页数据量(比如 10 条),不是全表总数;$users->total() 才是分页时那条 COUNT 查询的结果。但要注意:
- 子查询、UNION、LEFT JOIN 等场景下,COUNT 可能被框架误判,
total()偏小甚至为 0 - 此时应手动查总数:
$total = User::where(...)->count();,再传给 paginate:paginate(15, false, ['total' => $total]) -
$users->lastPage()依赖total(),总数不准会导致最后一页计算错误
分页的本质是「一次 COUNT + 一次 LIMIT」,所有问题几乎都源于这两条 SQL 没协同好——不是方法不会用,而是没让框架拿到可重写的查询源头。



















