paginate() 必须在查询构建阶段调用,链在 where/orderby 后,生成 limit/offset 和 count(*);simplepaginate() 仅查 limit+1,无总数,适合大表或无限滚动。

直接用 paginate() 就行,但必须在查询构建阶段调用,不能先 get() 再分页——否则全量数据进内存,分页就失效了。
paginate() 必须链在 where / orderBy 之后
所有过滤、排序逻辑得在 paginate() 前完成,它不是对集合做切片,而是改写 SQL 加 LIMIT 和 OFFSET,同时补一条 COUNT(*)。
- ✅ 正确:
User::where('active', 1)->orderBy('id')->paginate(10) - ❌ 错误:
User::where('active', 1)->get()->paginate(10)(报错或返回空分页器) - ⚠️ 复杂 JOIN 或
distinct时,COUNT(*)可能失败;可改用simplePaginate()跳过总数统计 - 排序字段必须稳定唯一,比如
orderBy('id'),避免翻页重复或漏数据
simplePaginate() 适合大表或无限滚动
它只执行一次带 LIMIT + 1 的查询,不查总数,所以没 last_page、total,但速度快、无 COUNT 瓶颈。
- 前端判断是否还有下一页,用
$posts->hasMorePages(),不是检查next_page_url - 返回的 URL 字段名是
nextPageUrl(驼峰),不是next_page_url,JSON 里取错会是null - 如果
data是空数组,不代表到底了——它可能只是 offset 超出范围,仍需结合hasMorePages()判断 - 不要在
simplePaginate()前加withTrashed()以外的全局作用域,否则行为不可控
API 返回分页 JSON 时别直接 return $paginator
LengthAwarePaginator 实例直接 json() 会暴露大量内部属性(如 path、per_page),前端通常只要 data、meta 结构。
- ✅ 推荐:
return response()->json($posts->toArray()) - ✅ 更干净:用 API Resource,重写
with()方法补全meta字段 - ⚠️
simplePaginate()的toArray()里last_page和total是null,前端做「共 X 条」展示会崩 - 如果用了自定义分页参数名(比如
'p'而非'page'),toArray()里meta.path不会自动更新,需手动withPath()
手动构造 LengthAwarePaginator 的坑
当你用 DB::select()、第三方 API 或缓存数据做分页时,才需要自己 new LengthAwarePaginator,它不自动切片、不校验页码、不处理 URL 参数。
- 第 1 个参数必须是已切好的当前页数组(
array_slice($all, $offset, $perPage)),不是原始集合 - 第 2 个参数是总条数(int),不是总页数;传错会导致页码链接跳转异常
- 当前页码从 1 开始,传 0 或负数会让生成的链接变成
?page=0,部分中间件会拦截 -
appends()和withPath()得手动传进构造参数的第 5 个数组里,例如['path' => '/api/users', 'query' => request()->query()]
最常被忽略的是:paginate() 的 COUNT(*) 在复杂查询里容易拖慢响应,而 simplePaginate() 又让前端失去总数感知——得根据场景权衡,而不是默认套用一个方法。











