コンテンツへスキップ

データベース:ページネーション

イントロダクション

他のフレームワークでは、ページネーションは非常に面倒なことがあります。Laravelのページネーションへのアプローチが、新鮮な空気になることを願っています。LaravelのPaginatorは、クエリビルダEloquent ORMに統合されており、設定ゼロでデータベースレコードの便利で使いやすいページネーションを提供します。

デフォルトで、Paginatorによって生成されるHTMLはTailwind CSSフレームワークと互換性がありますが、Bootstrapページネーションのサポートも利用可能です。

Tailwind JIT

LaravelのデフォルトのTailwindページネーションビューとTailwind JITエンジンを使用している場合は、アプリケーションのtailwind.config.jsファイルのcontentキーでLaravelのページネーションビューを参照し、Tailwindクラスがパージされないようにしてください。

1content: [
2 './resources/**/*.blade.php',
3 './resources/**/*.js',
4 './resources/**/*.vue',
5 './vendor/laravel/framework/src/Illuminate/Pagination/resources/views/*.blade.php',
6],

基本的な使い方

クエリビルダ結果のページネーション

アイテムをページ分割する方法はいくつかあります。最も簡単な方法は、クエリビルダまたはEloquentクエリpaginateメソッドを使用することです。paginateメソッドは、ユーザーが表示している現在のページに基づいて、クエリの「limit」と「offset」を自動的に設定します。デフォルトでは、現在のページはHTTPリクエストのpageクエリ文字列引数の値によって検出されます。この値はLaravelによって自動的に検出され、Paginatorによって生成されるリンクにも自動的に挿入されます。

この例では、paginateメソッドに渡す唯一の引数は、「1ページあたり」に表示したいアイテムの数です。この場合、1ページあたり15件のアイテムを表示するように指定しましょう。

1<?php
2 
3namespace App\Http\Controllers;
4 
5use App\Http\Controllers\Controller;
6use Illuminate\Support\Facades\DB;
7use Illuminate\View\View;
8 
9class UserController extends Controller
10{
11 /**
12 * Show all application users.
13 */
14 public function index(): View
15 {
16 return view('user.index', [
17 'users' => DB::table('users')->paginate(15)
18 ]);
19 }
20}

シンプルページネーション

paginateメソッドは、データベースからレコードを取得する前に、クエリに一致するレコードの総数をカウントします。これは、Paginatorが合計で何ページのレコードがあるかを知るために行われます。ただし、アプリケーションのUIに総ページ数を表示する予定がない場合、レコード数のカウントクエリは不要です。

したがって、アプリケーションのUIに単純な「次へ」と「前へ」のリンクを表示するだけでよい場合は、simplePaginateメソッドを使用して、効率的な単一のクエリを実行できます。

1$users = DB::table('users')->simplePaginate(15);

Eloquent結果のページネーション

Eloquentクエリをページ分割することもできます。この例では、App\Models\Userモデルをページ分割し、1ページあたり15件のレコードを表示するよう指定します。ご覧のとおり、構文はクエリビルダの結果をページ分割する場合とほぼ同じです。

1use App\Models\User;
2 
3$users = User::paginate(15);

もちろん、where句などの他の制約をクエリに設定した後に、paginateメソッドを呼び出すこともできます。

1$users = User::where('votes', '>', 100)->paginate(15);

Eloquentモデルをページ分割する場合も、simplePaginateメソッドを使用できます。

1$users = User::where('votes', '>', 100)->simplePaginate(15);

同様に、cursorPaginateメソッドを使用してEloquentモデルをカーソルページ分割できます。

1$users = User::where('votes', '>', 100)->cursorPaginate(15);

1ページあたり複数のPaginatorインスタンス

アプリケーションでレンダリングされる単一の画面に、2つの個別のPaginatorを表示する必要がある場合があります。しかし、両方のPaginatorインスタンスが現在のページを保存するためにpageクエリ文字列パラメータを使用している場合、2つのPaginatorが競合します。この競合を解決するには、paginatesimplePaginatecursorPaginateメソッドに提供する第3引数で、Paginatorの現在のページを保存するために使用したいクエリ文字列パラメータの名前を渡してください。

1use App\Models\User;
2 
3$users = User::where('votes', '>', 100)->paginate(
4 $perPage = 15, $columns = ['*'], $pageName = 'users'
5);

カーソルページネーション

paginatesimplePaginateがSQLの「offset」句を使用してクエリを作成するのに対し、カーソルページネーションは、クエリに含まれる順序付けられた列の値を比較する「where」句を構築することで機能し、Laravelのすべてのページネーションメソッドの中で最も効率的なデータベースパフォーマンスを提供します。このページネーション方法は、大規模なデータセットや「無限」スクロールのユーザーインターフェイスに特に適しています。

Paginatorによって生成されるURLのクエリ文字列にページ番号を含めるオフセットベースのページネーションとは異なり、カーソルベースのページネーションはクエリ文字列に「カーソル」文字列を配置します。カーソルは、次にページ分割するクエリがページ分割を開始すべき場所と、ページ分割すべき方向を含むエンコードされた文字列です。

1https:///users?cursor=eyJpZCI6MTUsIl9wb2ludHNUb05leHRJdGVtcyI6dHJ1ZX0

クエリビルダが提供するcursorPaginateメソッドを介して、カーソルベースのPaginatorインスタンスを作成できます。このメソッドはIlluminate\Pagination\CursorPaginatorのインスタンスを返します。

1$users = DB::table('users')->orderBy('id')->cursorPaginate(15);

カーソルPaginatorインスタンスを取得したら、paginateメソッドとsimplePaginateメソッドを使用するときと同様に、ページネーションの結果を表示できます。カーソルPaginatorが提供するインスタンスメソッドの詳細については、カーソルPaginatorインスタンスメソッドのドキュメントを参照してください。

カーソルページネーションを利用するには、クエリに「order by」句を含める必要があります。さらに、クエリの並べ替えに使用される列は、ページ分割しているテーブルに属している必要があります。

カーソルページネーション vs. オフセットページネーション

オフセットページネーションとカーソルページネーションの違いを説明するために、いくつかのSQLクエリの例を見てみましょう。次の両方のクエリは、idでソートされたusersテーブルの結果の「2ページ目」を表示します。

1# Offset Pagination...
2select * from users order by id asc limit 15 offset 15;
3 
4# Cursor Pagination...
5select * from users where id > 15 order by id asc limit 15;

カーソルページネーションクエリは、オフセットページネーションに比べて次の利点があります。

  • 大規模なデータセットの場合、「order by」列にインデックスが付けられていれば、カーソルページネーションの方がパフォーマンスが向上します。これは、「offset」句が以前に一致したすべてのデータをスキャンするためです。
  • 書き込みが頻繁に行われるデータセットの場合、ユーザーが現在表示しているページに最近結果が追加または削除された場合、オフセットページネーションではレコードがスキップされたり、重複が表示されたりすることがあります。

ただし、カーソルページネーションには次の制限があります。

  • simplePaginateと同様に、カーソルページネーションは「次へ」と「前へ」のリンクを表示するためにのみ使用でき、ページ番号付きのリンクの生成はサポートしていません。
  • 少なくとも1つのユニークな列、またはユニークな列の組み合わせに基づく順序付けが必要です。null値を持つ列はサポートされていません。
  • 「order by」句のクエリ式は、エイリアスが付けられ、「select」句にも追加されている場合にのみサポートされます。
  • パラメータ付きのクエリ式はサポートされていません。

手動でのPaginator作成

メモリ内にすでにあるアイテムの配列を渡して、手動でページネーションインスタンスを作成したい場合があります。必要に応じて、Illuminate\Pagination\PaginatorIlluminate\Pagination\LengthAwarePaginator、またはIlluminate\Pagination\CursorPaginatorインスタンスを作成することで、これを実行できます。

PaginatorクラスとCursorPaginatorクラスは、結果セット内のアイテムの総数を知る必要はありません。しかし、このため、これらのクラスには最終ページのインデックスを取得するメソッドがありません。LengthAwarePaginatorは、Paginatorとほぼ同じ引数を受け入れますが、結果セット内のアイテムの総数を必要とします。

言い換えると、PaginatorはクエリビルダのsimplePaginateメソッドに対応し、CursorPaginatorcursorPaginateメソッドに対応し、LengthAwarePaginatorpaginateメソッドに対応します。

Paginatorインスタンスを手動で作成する場合は、Paginatorに渡す結果の配列を自分で「スライス」する必要があります。これを行う方法がわからない場合は、PHPのarray_slice関数を確認してください。

ページネーションURLのカスタマイズ

デフォルトでは、Paginatorによって生成されるリンクは、現在のリクエストのURIと一致します。しかし、PaginatorのwithPathメソッドを使用すると、リンクを生成するときにPaginatorが使用するURIをカスタマイズできます。たとえば、Paginatorにhttp://example.com/admin/users?page=Nのようなリンクを生成させたい場合は、withPathメソッドに/admin/usersを渡す必要があります。

1use App\Models\User;
2 
3Route::get('/users', function () {
4 $users = User::paginate(15);
5 
6 $users->withPath('/admin/users');
7 
8 // ...
9});

クエリ文字列値の追加

appendsメソッドを使用して、ページネーションリンクのクエリ文字列に追加できます。たとえば、各ページネーションリンクにsort=votesを追加するには、次のようにappendsを呼び出す必要があります。

1use App\Models\User;
2 
3Route::get('/users', function () {
4 $users = User::paginate(15);
5 
6 $users->appends(['sort' => 'votes']);
7 
8 // ...
9});

現在のリクエストのすべてのクエリ文字列値をページネーションリンクに追加したい場合は、withQueryStringメソッドを使用できます。

1$users = User::paginate(15)->withQueryString();

ハッシュフラグメントの追加

Paginatorによって生成されるURLに「ハッシュフラグメント」を追加する必要がある場合は、fragmentメソッドを使用できます。たとえば、各ページネーションリンクの末尾に#usersを追加するには、次のようにfragmentメソッドを呼び出す必要があります。

1$users = User::paginate(15)->fragment('users');

ページネーション結果の表示

paginateメソッドを呼び出すと、Illuminate\Pagination\LengthAwarePaginatorのインスタンスを受け取ります。一方、simplePaginateメソッドを呼び出すと、Illuminate\Pagination\Paginatorのインスタンスが返されます。そして最後に、cursorPaginateメソッドを呼び出すと、Illuminate\Pagination\CursorPaginatorのインスタンスが返されます。

これらのオブジェクトは、結果セットを記述するいくつかのメソッドを提供します。これらのヘルパーメソッドに加えて、Paginatorインスタンスはイテレータであり、配列のようにループ処理できます。したがって、結果を取得したら、Bladeを使用して結果を表示し、ページリンクをレンダリングできます。

1<div class="container">
2 @foreach ($users as $user)
3 {{ $user->name }}
4 @endforeach
5</div>
6 
7{{ $users->links() }}

linksメソッドは、結果セットの残りのページへのリンクをレンダリングします。これらの各リンクには、適切なpageクエリ文字列変数がすでに含まれています。linksメソッドによって生成されるHTMLは、Tailwind CSSフレームワークと互換性があることを忘れないでください。

Paginatorがページネーションリンクを表示するとき、現在のページ番号と、現在のページの前後の3ページのリンクが表示されます。onEachSideメソッドを使用して、Paginatorによって生成されるリンクの中央のスライディングウィンドウ内で、現在のページの両側に表示される追加のリンク数を制御できます。

1{{ $users->onEachSide(5)->links() }}

結果のJSONへの変換

LaravelのPaginatorクラスは、Illuminate\Contracts\Support\Jsonableインターフェイスコントラクトを実装し、toJsonメソッドを公開しているため、ページネーションの結果をJSONに変換するのは非常に簡単です。Paginatorインスタンスをルートまたはコントローラアクションから返すことで、JSONに変換することもできます。

1use App\Models\User;
2 
3Route::get('/users', function () {
4 return User::paginate();
5});

PaginatorからのJSONには、totalcurrent_pagelast_pageなどのメタ情報が含まれます。結果のレコードは、JSON配列のdataキーを介して利用できます。以下は、ルートからPaginatorインスタンスを返すことによって作成されたJSONの例です。

1{
2 "total": 50,
3 "per_page": 15,
4 "current_page": 1,
5 "last_page": 4,
6 "first_page_url": "http://laravel.app?page=1",
7 "last_page_url": "http://laravel.app?page=4",
8 "next_page_url": "http://laravel.app?page=2",
9 "prev_page_url": null,
10 "path": "http://laravel.app",
11 "from": 1,
12 "to": 15,
13 "data":[
14 {
15 // Record...
16 },
17 {
18 // Record...
19 }
20 ]
21}

ページネーションビューのカスタマイズ

デフォルトでは、ページネーションリンクを表示するためにレンダリングされるビューは、Tailwind CSSフレームワークと互換性があります。ただし、Tailwindを使用していない場合は、これらのリンクをレンダリングするために独自のビューを自由に定義できます。Paginatorインスタンスでlinksメソッドを呼び出すときに、メソッドの最初の引数としてビュー名を渡すことができます。

1{{ $paginator->links('view.name') }}
2 
3<!-- Passing additional data to the view... -->
4{{ $paginator->links('view.name', ['foo' => 'bar']) }}

ただし、ページネーションビューをカスタマイズする最も簡単な方法は、vendor:publishコマンドを使用して、それらをresources/views/vendorディレクトリにエクスポートすることです。

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

このコマンドは、ビューをアプリケーションのresources/views/vendor/paginationディレクトリに配置します。このディレクトリ内のtailwind.blade.phpファイルは、デフォルトのページネーションビューに対応します。このファイルを編集して、ページネーションHTMLを変更できます。

別のファイルをデフォルトのページネーションビューとして指定したい場合は、App\Providers\AppServiceProviderクラスのbootメソッド内で、PaginatorのdefaultViewおよびdefaultSimpleViewメソッドを呼び出すことができます。

1<?php
2 
3namespace App\Providers;
4 
5use Illuminate\Pagination\Paginator;
6use Illuminate\Support\ServiceProvider;
7 
8class AppServiceProvider extends ServiceProvider
9{
10 /**
11 * Bootstrap any application services.
12 */
13 public function boot(): void
14 {
15 Paginator::defaultView('view-name');
16 
17 Paginator::defaultSimpleView('view-name');
18 }
19}

Bootstrapの使用

Laravelには、Bootstrap CSSを使用して構築されたページネーションビューが含まれています。デフォルトのTailwindビューの代わりにこれらのビューを使用するには、App\Providers\AppServiceProviderクラスのbootメソッド内で、PaginatorのuseBootstrapFourまたはuseBootstrapFiveメソッドを呼び出すことができます。

1use Illuminate\Pagination\Paginator;
2 
3/**
4 * Bootstrap any application services.
5 */
6public function boot(): void
7{
8 Paginator::useBootstrapFive();
9 Paginator::useBootstrapFour();
10}

Paginator / LengthAwarePaginatorインスタンスのメソッド

各Paginatorインスタンスは、次のメソッドを介して追加のページネーション情報を提供します。

メソッド 説明
$paginator->count() 現在のページのアイテム数を取得します。
$paginator->currentPage() 現在のページ番号を取得します。
$paginator->firstItem() 結果の最初のアイテムの結果番号を取得します。
$paginator->getOptions() Paginatorのオプションを取得します。
$paginator->getUrlRange($start, $end) ページネーションURLの範囲を作成します。
$paginator->hasPages() 複数のページに分割するのに十分なアイテムがあるかどうかを判断します。
$paginator->hasMorePages() データストアにさらにアイテムがあるかどうかを判断します。
$paginator->items() 現在のページのアイテムを取得します。
$paginator->lastItem() 結果の最後のアイテムの結果番号を取得します。
$paginator->lastPage() 利用可能な最後のページのページ番号を取得します。(simplePaginateを使用している場合は利用できません)。
$paginator->nextPageUrl() 次のページのURLを取得します。
$paginator->onFirstPage() Paginatorが最初のページにあるかどうかを判断します。
$paginator->perPage() 1ページあたりに表示されるアイテムの数。
$paginator->previousPageUrl() 前のページのURLを取得します。
$paginator->total() データストア内の一致するアイテムの総数を決定します。(simplePaginateを使用している場合は利用できません)。
$paginator->url($page) 指定されたページ番号のURLを取得します。
$paginator->getPageName() ページを保存するために使用されるクエリ文字列変数を取得します。
$paginator->setPageName($name) ページを保存するために使用されるクエリ文字列変数を設定します。
$paginator->through($callback) コールバックを使用して各アイテムを変換します。

カーソルPaginatorインスタンスのメソッド

各カーソルPaginatorインスタンスは、次のメソッドを介して追加のページネーション情報を提供します。

メソッド 説明
$paginator->count() 現在のページのアイテム数を取得します。
$paginator->cursor() 現在のカーソルインスタンスを取得します。
$paginator->getOptions() Paginatorのオプションを取得します。
$paginator->hasPages() 複数のページに分割するのに十分なアイテムがあるかどうかを判断します。
$paginator->hasMorePages() データストアにさらにアイテムがあるかどうかを判断します。
$paginator->getCursorName() カーソルを保存するために使用されるクエリ文字列変数を取得します。
$paginator->items() 現在のページのアイテムを取得します。
$paginator->nextCursor() 次のアイテムセットのカーソルインスタンスを取得します。
$paginator->nextPageUrl() 次のページのURLを取得します。
$paginator->onFirstPage() Paginatorが最初のページにあるかどうかを判断します。
$paginator->onLastPage() Paginatorが最後のページにあるかどうかを判断します。
$paginator->perPage() 1ページあたりに表示されるアイテムの数。
$paginator->previousCursor() 前のアイテムセットのカーソルインスタンスを取得します。
$paginator->previousPageUrl() 前のページのURLを取得します。
$paginator->setCursorName() カーソルを保存するために使用されるクエリ文字列変数を設定します。
$paginator->url($cursor) 指定されたカーソルインスタンスのURLを取得します。

Laravelは最も生産的な方法です
ソフトウェアを構築、デプロイ、監視します。