W

Laravelのページネーション実装方法|一覧を1ページずつ表示する

#Laravel #ページネーション #Eloquent #Blade

記事一覧やユーザー一覧を作ったとき、データが増えると1つの画面に何百件も並んでしまいます。表示は遅くなり、読む側も探しづらくなります。

そこで使うのがページネーション(ページ分割)です。「1ページ10件ずつ表示して、下に[1][2][3]…のリンクを出す」あの仕組みです。

Laravel には最初から用意されていて、驚くほど短いコードで実装できます。

完成形(先に全体像)

やることは2つだけです。

  1. コントローラで all() の代わりに paginate() を使う
  2. Blade で {{ $posts->links() }} を書く

これだけでページ送りのリンクまで自動で作られます。順に見ていきます。

1. コントローラ側

まず、データを取得している部分を書き換えます。

// 変更前: 全件取得
$posts = Post::all();

// 変更後: 10件ずつに分割
$posts = Post::paginate(10);

paginate(10)10 が「1ページあたりの件数」です。コントローラのファイル全体では次のようになります。

<?php
// app/Http/Controllers/PostController.php

namespace App\Http\Controllers;

use App\Models\Post;

class PostController extends Controller
{
    public function index()
    {
        $posts = Post::latest()->paginate(10);

        return view('posts.index', ['posts' => $posts]);
    }
}

use App\Models\Post; を忘れずに。 この1行がないと、PHP は同じフォルダにある App\Http\Controllers\Post を探しにいってしまい、「Class “App\Http\Controllers\Post” not found」というエラーになります。初心者がとてもよく詰まるポイントです。

latest() は「新しい順に並べる」という指定です。並び順を決めてから paginate() を呼ぶ、と覚えてください。

ポイント: paginate() は「いま何ページ目を見ているか」を自分で判断します。URL の ?page=2 を Laravel が自動で読み取ってくれるので、こちらで書く必要はありません。

2. Blade 側(画面)

取得したデータは、これまでどおり @foreach で回せます。そこに1行足すだけです。

{{-- resources/views/posts/index.blade.php --}}
<ul>
    @foreach ($posts as $post)
        <li>{{ $post->title }}</li>
    @endforeach
</ul>

{{-- ページ送りのリンク --}}
{{ $posts->links() }}

{{ $posts->links() }} の部分に、[前へ][1][2][3][次へ]のようなリンクが自動で出力されます。実際には <nav> タグを含む HTML が生成されます。

見た目について: 出力される HTML には、標準で Tailwind CSS 用のクラスが付いています。そのため Tailwind を使っている構成(Laravel Breeze など)ならきれいに整いますが、CSS を何も読み込んでいない状態では、リンクが素の状態で並ぶだけです。「リンクは出たがデザインが崩れている」という場合は、この後の「見た目を Bootstrap に変える」を参照してください。

件数や状態を取り出す

「全何件中、何件目を表示中」といった情報も取り出せます。

<p>
{{ $posts->total() }} 件中
    {{ $posts->firstItem() }}{{ $posts->lastItem() }} 件目を表示
{{ $posts->currentPage() }} / {{ $posts->lastPage() }} ページ)
</p>

よく使うものをまとめます。

メソッド返すもの
total()全体の件数
count()いまのページの件数
perPage()1ページあたりの件数
currentPage()いま何ページ目か
lastPage()最後のページ番号
hasPages()ページ分割が必要か(1ページに収まるなら false)

paginate()simplePaginate() の違い

似たメソッドに simplePaginate() があります。

$posts = Post::simplePaginate(10);

違いは次のとおりです。

paginate()simplePaginate()
リンクの見た目[1][2][3]…と番号が出る[前へ][次へ]だけ
全体の件数分かる(total() が使える)分からない
データベースへの負荷やや重い(全件を数えるため)軽い

paginate() は「全部で何件あるか」を知るために、件数を数える処理を追加で実行します。データが非常に多いときはこれが負担になります。

使い分けの目安

  • 件数を見せたい、ページ番号を出したい → paginate()
  • とにかく速くしたい、件数は不要 → simplePaginate()

迷ったら paginate() で構いません。

つまずきポイント: 検索条件が消える

実務でほぼ必ず遭遇する問題です。

検索フォームを付けて ?keyword=laravel のような URL で絞り込んだあと、2ページ目のリンクを押すと検索条件が消えてしまうことがあります。リンクの URL が ?page=2 だけになってしまうためです。

これは withQueryString() を付けると解決します。コントローラの全体像はこうなります。

<?php
// app/Http/Controllers/PostController.php

namespace App\Http\Controllers;

use App\Models\Post;
use Illuminate\Http\Request;

class PostController extends Controller
{
    public function index(Request $request)
    {
        // URLの ?keyword=... を受け取る(無ければ null)
        $keyword = $request->input('keyword');

        $posts = Post::when($keyword, function ($query, $keyword) {
                $query->where('title', 'like', "%{$keyword}%");
            })
            ->latest()
            ->paginate(10)
            ->withQueryString();

        return view('posts.index', ['posts' => $posts]);
    }
}

when() は「第1引数が空でなければ、中の処理を実行する」という便利なメソッドです。これで、検索語が入力されたときだけ絞り込みが効きます。

withQueryString() を付けたことで、ページ送りのリンクが ?keyword=laravel&page=2 のように、いまの検索条件を引き継いだ URL になります。

補足: like 検索では、%_ が特別な意味を持つ記号(ワイルドカード)として扱われます。ユーザーが検索語に 100% のような文字を入れると、意図と違う結果になることがあります。厳密に扱いたい場合は、これらの記号をエスケープする処理が別途必要です。

特定のパラメータだけを付けたい場合は appends() を使います。

$posts = Post::paginate(10)->appends(['sort' => 'new']);

見た目を Bootstrap に変える

標準では Tailwind CSS 向けの HTML が出力されます。Bootstrap を使っているサイトでは、AppServiceProvider で切り替えられます。

// app/Providers/AppServiceProvider.php
use Illuminate\Pagination\Paginator;

public function boot(): void
{
    Paginator::useBootstrapFive();
}

デザインを自分で作り込みたい場合は、ページネーション用のビューを自分のプロジェクトに取り出して編集できます。

php artisan vendor:publish --tag=laravel-pagination

実行すると resources/views/vendor/pagination/ にテンプレートがコピーされ、自由に書き換えられるようになります。

表示するリンクの数を調整する

ページ数が多いと、リンクが横に長く伸びてしまいます。現在ページの左右に表示する数は onEachSide() で調整できます。

$posts = Post::paginate(10)->onEachSide(2);

まとめ

  • コントローラで paginate(件数)、Blade で {{ $posts->links() }}。この2つだけで実装できる。
  • ?page=2 は Laravel が自動で読み取るので、自分で処理する必要はない。
  • 件数を出したいときは total()currentPage() を使う。
  • 番号リンクが要らない・速さ優先なら simplePaginate()(ただし total() は使えない)。
  • 検索条件を保ちたいときは withQueryString()。これを忘れるとページ送りで条件が消える。
  • 見た目は useBootstrapFive()vendor:publish でカスタマイズできる。

一覧・登録・編集・削除をひととおり作る流れは、カリキュラムの 一覧・登録・編集・削除を実装する で解説しています。