- はじめに
- 基本的な使い方
- ページネーション結果の表示
- ページネーションビューのカスタマイズ
- Paginator と LengthAwarePaginator のインスタンスメソッド
- Cursor Paginator のインスタンスメソッド
#はじめに
他のフレームワークではページネーションが非常に面倒なことがありますが、Laravelのページネーションは新鮮な体験になるはずです。LaravelのページネーターはクエリビルダーやEloquent ORMと統合されており、設定不要で簡単にデータベースレコードのページネーションができます。
デフォルトでは、ページネーターが生成するHTMLはTailwind CSSフレームワークに対応していますが、Bootstrapのページネーションもサポートされています。
#Tailwind JIT
LaravelのデフォルトのTailwindページネーションビューとTailwind JITエンジンを使用している場合は、tailwind.config.jsファイルのcontentキーにLaravelのページネーションビューを参照させて、Tailwindのクラスが削除されないようにしてください。
content: [
'./resources/**/*.blade.php',
'./resources/**/*.js',
'./resources/**/*.vue',
'./vendor/laravel/framework/src/Illuminate/Pagination/resources/views/*.blade.php',
],
#基本的な使い方
#クエリビルダーの結果をページネーションする
アイテムをページネーションする方法はいくつかあります。最も簡単なのは、クエリビルダーやEloquentクエリのpaginateメソッドを使うことです。paginateメソッドは、現在ユーザーが見ているページに基づいて自動的にクエリの「limit」と「offset」を設定します。デフォルトでは、現在のページはHTTPリクエストのpageクエリ文字列引数の値から検出されます。この値はLaravelが自動的に検出し、ページネーターが生成するリンクにも自動的に挿入されます。
この例では、paginateメソッドに渡す唯一の引数は「1ページあたりに表示したいアイテム数」です。ここでは、1ページあたり15件表示するよう指定します。
<?php
namespace App\Http\Controllers;
use App\Http\Controllers\Controller;
use Illuminate\Support\Facades\DB;
use Illuminate\View\View;
class UserController extends Controller
{
/**
* アプリケーションの全ユーザーを表示する。
*/
public function index(): View
{
return view('user.index', [
'users' => DB::table('users')->paginate(15)
]);
}
}
#シンプルページネーション
paginateメソッドは、データベースからレコードを取得する前に、クエリにマッチするレコードの総数をカウントします。これはページネーターが総ページ数を把握するためです。ただし、アプリケーションのUIで総ページ数を表示しない場合は、このレコード数のカウントは不要です。
そのため、UIに「次へ」と「前へ」のリンクだけを表示したい場合は、simplePaginateメソッドを使って単一の効率的なクエリを実行できます。
$users = DB::table('users')->simplePaginate(15);
#Eloquentの結果をページネーションする
Eloquentクエリもページネーションできます。この例では、App\Models\Userモデルをページネーションし、1ページあたり15件表示することを示しています。クエリビルダーのページネーションとほぼ同じ構文です。
use App\Models\User;
$users = User::paginate(15);
もちろん、where句など他の制約を設定した後にpaginateメソッドを呼び出すこともできます。
$users = User::where('votes', '>', 100)->paginate(15);
EloquentモデルのページネーションでもsimplePaginateメソッドを使えます。
$users = User::where('votes', '>', 100)->simplePaginate(15);
同様に、cursorPaginateメソッドを使ってEloquentモデルをカーソルページネーションできます。
$users = User::where('votes', '>', 100)->cursorPaginate(15);
#1ページに複数のページネーターを表示する
アプリケーションの画面に2つの別々のページネーターを表示する必要がある場合があります。しかし、両方のページネーターがpageクエリ文字列パラメータを使うと競合します。この競合を避けるために、paginate、simplePaginate、cursorPaginateメソッドの第3引数にページネーターの現在ページを格納するクエリ文字列パラメータ名を渡せます。
use App\Models\User;
$users = User::where('votes', '>', 100)->paginate(
$perPage = 15, $columns = ['*'], $pageName = 'users'
);
#カーソルページネーション
paginateやsimplePaginateはSQLの「offset」句を使ってクエリを作成しますが、カーソルページネーションはクエリ内の並び順のカラム値を比較する「where」句を構築します。これにより、Laravelのページネーション方法の中で最も効率的なデータベースパフォーマンスを実現します。大規模データセットや「無限スクロール」UIに特に適しています。
オフセットベースのページネーションはページ番号をURLのクエリ文字列に含めますが、カーソルページネーションは「カーソル」文字列をクエリ文字列に含めます。カーソルは次のページネーション開始位置と方向をエンコードした文字列です。
http://localhost/users?cursor=eyJpZCI6MTUsIl9wb2ludHNUb05leHRJdGVtcyI6dHJ1ZX0
クエリビルダーのcursorPaginateメソッドでカーソルベースのページネーターインスタンスを作成できます。このメソッドはIlluminate\Pagination\CursorPaginatorのインスタンスを返します。
$users = DB::table('users')->orderBy('id')->cursorPaginate(15);
カーソルページネーターインスタンスを取得したら、paginateやsimplePaginateと同様にページネーション結果を表示できます。カーソルページネーターのインスタンスメソッドの詳細はカーソルページネーターのインスタンスメソッドのドキュメントを参照してください。
クエリに「order by」句が含まれている必要があります。また、並び替えに使うカラムはページネーション対象のテーブルのカラムでなければなりません。
#カーソルページネーションとオフセットページネーションの違い
オフセットページネーションとカーソルページネーションの違いを示すために、以下のSQLクエリ例を見てみましょう。どちらもusersテーブルをidで並び替えた「2ページ目」の結果を表示します。
# オフセットページネーション...
select * from users order by id asc limit 15 offset 15;
# カーソルページネーション...
select * from users where id > 15 order by id asc limit 15;
カーソルページネーションはオフセットページネーションに比べて以下の利点があります。
- 大規模データセットでは、「order by」カラムにインデックスがあればカーソルページネーションの方が高速です。オフセット句は過去のすべてのデータをスキャンするためです。
- 頻繁に書き込みがあるデータセットでは、オフセットページネーションはユーザーが見ているページに最近追加・削除されたレコードをスキップしたり重複表示したりする可能性があります。
ただし、カーソルページネーションには以下の制限があります。
simplePaginateと同様に、「次へ」「前へ」リンクのみ表示でき、ページ番号付きリンクは生成できません。- 並び順は少なくとも1つのユニークなカラム、またはユニークなカラムの組み合わせに基づく必要があります。
null値のカラムはサポートされません。 - 「order by」句のクエリ式は、エイリアスが付けられ「select」句にも含まれている場合のみサポートされます。
- パラメータ付きのクエリ式はサポートされません。
#手動でページネーターを作成する
メモリ上にあるアイテムの配列を渡してページネーションインスタンスを手動で作成したい場合があります。必要に応じてIlluminate\Pagination\Paginator、Illuminate\Pagination\LengthAwarePaginator、Illuminate\Pagination\CursorPaginatorのいずれかのインスタンスを作成できます。
PaginatorとCursorPaginatorクラスは結果セットの総アイテム数を知る必要がありませんが、そのため最後のページのインデックスを取得するメソッドはありません。LengthAwarePaginatorはPaginatorとほぼ同じ引数を受け取りますが、結果セットの総アイテム数を必須とします。
つまり、PaginatorはクエリビルダーのsimplePaginateメソッドに対応し、CursorPaginatorはcursorPaginateメソッドに対応し、LengthAwarePaginatorはpaginateメソッドに対応します。
ページネーターインスタンスを手動で作成する場合は、渡す結果の配列を自分で「スライス」する必要があります。方法がわからない場合は、PHPのarray_slice関数を参照してください。
#ページネーションURLのカスタマイズ
デフォルトでは、ページネーターが生成するリンクは現在のリクエストURIに一致します。しかし、withPathメソッドを使うとリンク生成時に使うURIをカスタマイズできます。例えば、http://example.com/admin/users?page=Nのようなリンクを生成したい場合は、withPathに/admin/usersを渡します。
use App\Models\User;
Route::get('/users', function () {
$users = User::paginate(15);
$users->withPath('/admin/users');
// ...
});
#クエリ文字列の値を追加する
appendsメソッドを使うとページネーションリンクのクエリ文字列に値を追加できます。例えば、すべてのページネーションリンクにsort=votesを追加するには、appendsを次のように呼び出します。
use App\Models\User;
Route::get('/users', function () {
$users = User::paginate(15);
$users->appends(['sort' => 'votes']);
// ...
});
現在のリクエストのすべてのクエリ文字列値をページネーションリンクに追加したい場合は、withQueryStringメソッドを使います。
$users = User::paginate(15)->withQueryString();
#ハッシュフラグメントを追加する
ページネーターが生成するURLに「ハッシュフラグメント」を追加したい場合は、fragmentメソッドを使います。例えば、すべてのページネーションリンクの末尾に#usersを追加するには、次のようにfragmentを呼び出します。
$users = User::paginate(15)->fragment('users');
#ページネーション結果の表示
paginateメソッドを呼ぶとIlluminate\Pagination\LengthAwarePaginatorのインスタンスが返り、simplePaginateはIlluminate\Pagination\Paginatorのインスタンス、cursorPaginateはIlluminate\Pagination\CursorPaginatorのインスタンスを返します。
これらのオブジェクトは結果セットを説明するいくつかのメソッドを提供します。さらに、ページネーターインスタンスはイテレーターであり、配列のようにループ処理できます。結果を取得したら、Bladeを使って結果を表示し、ページリンクをレンダリングできます。
<div class="container">
@foreach ($users as $user)
{{ $user->name }}
@endforeach
</div>
{{ $users->links() }}
linksメソッドは結果セットの他のページへのリンクをレンダリングします。これらのリンクにはすでに適切なpageクエリ文字列変数が含まれています。linksメソッドが生成するHTMLはTailwind CSSフレームワークに対応しています。
#ページネーションリンクの表示範囲を調整する
ページネーターがページネーションリンクを表示するとき、現在のページ番号とともに、現在のページの前後3ページ分のリンクも表示されます。onEachSide メソッドを使うと、ページネーターが生成する中央のスライドウィンドウ内で、現在のページの両側に表示する追加リンクの数を制御できます。
{{ $users->onEachSide(5)->links() }}
#結果をJSONに変換する
Laravelのページネータークラスは Illuminate\Contracts\Support\Jsonable インターフェイスを実装しており、toJson メソッドを提供しているため、ページネーション結果を簡単にJSONに変換できます。ルートやコントローラーアクションからページネーターインスタンスを返すことで、JSONに変換することも可能です。
use App\Models\User;
Route::get('/users', function () {
return User::paginate();
});
ページネーターからのJSONには、total、current_page、last_page などのメタ情報が含まれます。結果のレコードはJSON配列の data キーで取得できます。以下はルートからページネーターインスタンスを返したときに生成されるJSONの例です。
{
"total": 50,
"per_page": 15,
"current_page": 1,
"last_page": 4,
"first_page_url": "http://laravel.app?page=1",
"last_page_url": "http://laravel.app?page=4",
"next_page_url": "http://laravel.app?page=2",
"prev_page_url": null,
"path": "http://laravel.app",
"from": 1,
"to": 15,
"data":[
{
// Record...
},
{
// Record...
}
]
}
#ページネーションビューのカスタマイズ
デフォルトでは、ページネーションリンクを表示するビューは Tailwind CSS フレームワークに対応しています。ただし、Tailwindを使っていない場合は、独自のビューを定義してこれらのリンクをレンダリングできます。ページネーターインスタンスの links メソッドを呼ぶ際に、ビュー名を最初の引数として渡せます。
{{ $paginator->links('view.name') }}
<!-- ビューに追加データを渡す場合... -->
{{ $paginator->links('view.name', ['foo' => 'bar']) }}
しかし、ページネーションビューをカスタマイズする最も簡単な方法は、vendor:publish コマンドを使ってビューを resources/views/vendor ディレクトリにエクスポートすることです。
php artisan vendor:publish --tag=laravel-pagination
このコマンドは、アプリケーションの resources/views/vendor/pagination ディレクトリにビューを配置します。このディレクトリ内の tailwind.blade.php ファイルがデフォルトのページネーションビューに対応しています。このファイルを編集してページネーションのHTMLを変更できます。
別のファイルをデフォルトのページネーションビューとして指定したい場合は、App\Providers\AppServiceProvider クラスの boot メソッド内でページネーターの defaultView と defaultSimpleView メソッドを呼び出せます。
<?php
namespace App\Providers;
use Illuminate\Pagination\Paginator;
use Illuminate\Support\ServiceProvider;
class AppServiceProvider extends ServiceProvider
{
/**
* アプリケーションサービスのブートストラップ処理。
*/
public function boot(): void
{
Paginator::defaultView('view-name');
Paginator::defaultSimpleView('view-name');
}
}
#Bootstrapの使用
Laravelには Bootstrap CSS を使ったページネーションビューが含まれています。デフォルトのTailwindビューの代わりにこれらのビューを使うには、App\Providers\AppServiceProvider クラスの boot メソッド内でページネーターの useBootstrapFour または useBootstrapFive メソッドを呼び出します。
use Illuminate\Pagination\Paginator;
/**
* アプリケーションサービスのブートストラップ処理。
*/
public function boot(): void
{
Paginator::useBootstrapFive();
Paginator::useBootstrapFour();
}
#Paginator / LengthAwarePaginator インスタンスメソッド
各ページネーターインスタンスは、以下のメソッドで追加のページネーション情報を提供します。
| メソッド | 説明 |
|---|---|
$paginator->count() |
現在のページのアイテム数を取得します。 |
$paginator->currentPage() |
現在のページ番号を取得します。 |
$paginator->firstItem() |
結果の最初のアイテムの番号を取得します。 |
$paginator->getOptions() |
ページネーターのオプションを取得します。 |
$paginator->getUrlRange($start, $end) |
ページネーションURLの範囲を作成します。 |
$paginator->hasPages() |
複数ページに分割するのに十分なアイテムがあるか判定します。 |
$paginator->hasMorePages() |
データストアにさらにアイテムがあるか判定します。 |
$paginator->items() |
現在のページのアイテムを取得します。 |
$paginator->lastItem() |
結果の最後のアイテムの番号を取得します。 |
$paginator->lastPage() |
最後の利用可能なページ番号を取得します。(simplePaginate 使用時は利用不可) |
$paginator->nextPageUrl() |
次のページのURLを取得します。 |
$paginator->onFirstPage() |
ページネーターが最初のページか判定します。 |
$paginator->perPage() |
1ページあたりの表示アイテム数です。 |
$paginator->previousPageUrl() |
前のページのURLを取得します。 |
$paginator->total() |
データストア内の該当アイテムの総数を判定します。(simplePaginate 使用時は利用不可) |
$paginator->url($page) |
指定したページ番号のURLを取得します。 |
$paginator->getPageName() |
ページ番号を格納するクエリ文字列の変数名を取得します。 |
$paginator->setPageName($name) |
ページ番号を格納するクエリ文字列の変数名を設定します。 |
$paginator->through($callback) |
コールバックを使って各アイテムを変換します。 |
#Cursor Paginator インスタンスメソッド
各カーソルページネーターインスタンスは、以下のメソッドで追加のページネーション情報を提供します。
| メソッド | 説明 |
|---|---|
$paginator->count() |
現在のページのアイテム数を取得します。 |
$paginator->cursor() |
現在のカーソルインスタンスを取得します。 |
$paginator->getOptions() |
ページネーターのオプションを取得します。 |
$paginator->hasPages() |
複数ページに分割するのに十分なアイテムがあるか判定します。 |
$paginator->hasMorePages() |
データストアにさらにアイテムがあるか判定します。 |
$paginator->getCursorName() |
カーソルを格納するクエリ文字列の変数名を取得します。 |
$paginator->items() |
現在のページのアイテムを取得します。 |
$paginator->nextCursor() |
次のアイテムセットのカーソルインスタンスを取得します。 |
$paginator->nextPageUrl() |
次のページのURLを取得します。 |
$paginator->onFirstPage() |
ページネーターが最初のページか判定します。 |
$paginator->onLastPage() |
ページネーターが最後のページか判定します。 |
$paginator->perPage() |
1ページあたりの表示アイテム数です。 |
$paginator->previousCursor() |
前のアイテムセットのカーソルインスタンスを取得します。 |
$paginator->previousPageUrl() |
前のページのURLを取得します。 |
$paginator->setCursorName() |
カーソルを格納するクエリ文字列の変数名を設定します。 |
$paginator->url($cursor) |
指定したカーソルインスタンスのURLを取得します。 |