LaravelのPaginationエラーの原因と対処法
- 作成日 2025.12.19
- その他
Laravelのページネーション周りのエラーは、「paginatorに渡している値が想定と違う」「URLやクエリ文字列の組み立てが崩れている」「DBクエリがページングに向いていない(GROUP BY/UNION/サブクエリ)」「Bladeの描画方法が合っていない」「API/SPAでレスポンス形式がズレている」といった原因で起きやすい。エラー文言は環境ごとにバラバラになりがちなので、まずはどの段階(クエリ生成・件数カウント・ページリンク生成・View描画・API返却)で落ちているかを切り分けると解決が速い。
- 1. よくあるPaginationエラーの例と発生条件
- 2. 原因1:get() してから links() を呼んでいる(Collectionにlinksは無い)
- 3. 原因2:paginate() を呼ぶ場所が違う(Collection/Modelにpaginateは無い)
- 4. 原因3:検索条件やソートがページ移動で消える(クエリ文字列維持のミス)
- 5. 原因4:API/SPAでのページネーション形式のズレ(links/metaが無い等)
- 6. 原因5:GROUP BY / DISTINCT / UNION / サブクエリでcountクエリが壊れる
- 7. 原因6:ORDER BY が無く、ページごとに並び順が揺れる(重複/抜けが出る)
- 8. サンプル:検索+ソート+ページネーション(クエリ維持込み)
- 9. チェックリスト(上から順に確認する)
よくあるPaginationエラーの例と発生条件
ページネーション関連で頻出する例。
Call to undefined method Illuminate\Support\Collection::links()
Method Illuminate\Database\Eloquent\Collection::paginate does not exist.
Too few arguments to function Illuminate\Pagination\LengthAwarePaginator::__construct()
Undefined variable: paginator
Property [links] does not exist on this collection instance.
SQLSTATE[...] (count用のSQLが失敗する:GROUP BY/UNION/サブクエリ絡み)
発生条件の典型:
・get() した「Collection」に対して links() を呼んでいる
・paginate() を「Collection」や「Modelインスタンス」に対して呼んでいる
・手動でPaginatorを作っているが引数が不足/型が違う
・APIで paginate() の結果をそのまま返しているのにフロント側が期待する形と違う
・GROUP BY / DISTINCT / UNION / サブクエリが入っていて、paginateが内部で作るcountクエリが落ちる
・ページリンクが /?page=2 にならず、検索条件が消える(クエリ文字列維持ミス)
原因1:get() してから links() を呼んでいる(Collectionにlinksは無い)
最も多いパターン。get() は「全部取る」なので、返るのは Collection。ページネーションではない。
// NG: get() した結果は Collection
$users = User::query()->orderBy('id')->get();
return view('users.index', compact('users'));
// Blade
{{ $users->links() }} // Call to undefined method ...Collection::links()
対処:get() ではなく paginate() を使う。
// OK
$users = User::query()->orderBy('id')->paginate(20);
return view('users.index', compact('users'));
// Blade
{{ $users->links() }}「まず一覧を作って、途中で links を足したら落ちた」ケースはほぼこれ。
原因2:paginate() を呼ぶ場所が違う(Collection/Modelにpaginateは無い)
paginate() は Query Builder / Eloquent Builder のメソッド。CollectionやModelインスタンスには無い。
// NG: find() は Model(単体)を返す
$user = User::find(1);
$user->paginate(10); // Method ...::paginate does not exist
// NG: get() 後は Collection
$users = User::where('active', 1)->get();
$users->paginate(10); // Method ...Collection::paginate does not exist
対処:paginate() は get() の前、クエリ構築段階で呼ぶ。
// OK
$users = User::where('active', 1)->orderBy('id')->paginate(10);
原因3:検索条件やソートがページ移動で消える(クエリ文字列維持のミス)
検索フォーム(q=xxx)や並び替え(sort=created_at)を付けていると、ページ2へ行ったときに条件が消える事故が起きやすい。
発生条件:
・paginateのリンクにクエリが引き継がれていない
対処:withQueryString() または appends() を使う。
// OK: 現在のクエリ文字列を維持
$users = User::query()
->when(request('q'), fn($q) => $q->where('name', 'like', '%'.request('q').'%'))
->orderBy(request('sort', 'id'))
->paginate(20)
->withQueryString();必要なパラメータだけ維持したい場合。
$users = User::query()
->paginate(20)
->appends([
'q' => request('q'),
'sort' => request('sort'),
]);原因4:API/SPAでのページネーション形式のズレ(links/metaが無い等)
Laravelの paginate() は JSON で返すと、data と links/meta を含む構造になる。一方でフロントが「itemsだけ欲しい」設計だとズレる。
// 例: そのまま返す
return User::query()->paginate(20);
対処案:
・Laravel標準の形式(data, links, meta)にフロントを合わせる
・もしくは Resource で返却形式を固定する
Resource例。
use App\Http\Resources\UserResource;
return UserResource::collection(
User::query()->orderBy('id')->paginate(20)
)->additional([
'status' => 'ok',
]);itemsだけ返す設計なら、paginatorの情報も併せて返す方がページUIを作りやすい。
$p = User::query()->orderBy('id')->paginate(20);
return response()->json([
'items' => $p->items(),
'current_page' => $p->currentPage(),
'last_page' => $p->lastPage(),
'total' => $p->total(),
'per_page' => $p->perPage(),
]);原因5:GROUP BY / DISTINCT / UNION / サブクエリでcountクエリが壊れる
paginate() は内部で「総件数を数えるcountクエリ」を作る。複雑なクエリだと、このcountがDB側でエラーになることがある(SQLSTATE系)。
発生条件の典型:
・selectRaw + groupBy の組み合わせ
・union を含む
・distinct の対象が複雑
対処案(目的に応じて選ぶ):
・集計結果をページングしたいなら、集計後の結果をサブクエリ化してからpaginateする
・総件数が不要なら simplePaginate() にしてcountを避ける
// OK: countを省略して軽量化(総件数・最終ページは出ない)
$rows = DB::table('logs')
->select(['id', 'level', 'created_at'])
->orderByDesc('id')
->simplePaginate(50);集計をサブクエリ化する例(概念)。
$sub = DB::table('orders')
->selectRaw('user_id, COUNT(*) as cnt')
->groupBy('user_id');
$rows = DB::query()
->fromSub($sub, 't')
->orderByDesc('cnt')
->paginate(20);原因6:ORDER BY が無く、ページごとに並び順が揺れる(重複/抜けが出る)
エラーとして落ちないが「ページ2に行ったら同じデータが出る」「ページ移動で順序が変わる」系の“Pagination不具合”の本命。
発生条件:
・orderByなし
・orderByが非一意(created_atだけ等)で、同時刻データが多い
対処:必ず安定ソートを入れる(主キーを最後に追加)。
// OK: 安定ソート
$users = User::query()
->orderByDesc('created_at')
->orderByDesc('id')
->paginate(50);大量データや高頻度更新テーブルでは cursorPaginate() も選択肢。
$users = User::query()
->orderBy('id')
->cursorPaginate(50);サンプル:検索+ソート+ページネーション(クエリ維持込み)
検索条件がページ移動で消えない、順序が揺れない、という“事故りにくい”形。
use App\Models\User;
use Illuminate\Http\Request;
public function index(Request $request)
{
$q = $request->query('q');
$sort = $request->query('sort', 'id');
$dir = $request->query('dir', 'asc') === 'desc' ? 'desc' : 'asc';
$allowedSorts = ['id', 'name', 'created_at'];
if (!in_array($sort, $allowedSorts, true)) {
$sort = 'id';
}
$users = User::query()
->when($q, fn($qb) => $qb->where('name', 'like', '%'.$q.'%'))
->orderBy($sort, $dir)
->orderBy('id', 'asc') // 安定ソート
->paginate(20)
->withQueryString();
return view('users.index', compact('users', 'q', 'sort', 'dir'));
}チェックリスト(上から順に確認する)
1) links() を呼んでいる変数が paginate()/simplePaginate()/cursorPaginate() の戻り値か(get()のCollectionではないか)
2) paginate() を get() の後に呼んでいないか(Builder段階で呼んでいるか)
3) ページ移動で検索条件が消えるなら withQueryString()/appends() を使っているか
4) APIなら返却形式(data/links/meta)とフロントの期待が一致しているか
5) GROUP BY/UNION等があるなら countクエリが壊れていないか(simplePaginateで回避できないか)
6) orderBy が入っていて順序が安定しているか(同値が多いなら id も追加しているか)
-
前の記事
Laravel『TokenMismatchException』の原因と対処法 2025.12.18
-
次の記事
Laravel『Syntax Error or Access Violation』の原因と対処法 2025.12.20
コメントを書く