Laravelでページネーション

# 目次
Laravelの記事一覧は下記
[PHPフレームワークLaravelの使い方](https://qiita.com/toontoon/items/c4d0371e504c37f6576e)

# Laravelバージョン
動作確認はLaravel Framework 7.19.1で行っています

# 前提条件
[eclipseでLaravel開発環境を構築する。デバッグでブレークポイントをつけて止める。(WindowsもVagrantもdockerも)](https://qiita.com/toontoon/items/76fa7f8845b20776077c)
本記事は上記が完了している前提で書かれています
プロジェクトの作成もapacheの設定も上記で行っています

[Laravelでデータベースを操作する(クエリビルダ編)](https://qiita.com/toontoon/items/b38f6aa89316ef1ee4b7)
本記事は上記が完了している前提で書かれています
本記事は上記で作成したフォルダとファイルを使用します

# サービスクラス修正
ここで修正するサービスクラスは
[Laravelでデータベースを操作する(クエリビルダ編)](https://qiita.com/toontoon/items/b38f6aa89316ef1ee4b7)
で作成したものです
(1) /sample/app/Services/Interfaces/Table2Service.php修正
selectメソッドの引数修正
引数に`$limit = null, $page = null, $sort = null`追加

```Table2Service.php
‥‥
    public function select($id, $varchar_col, $int_col, $datetime_col, $date_col, $time_col, $limit = null, $page = null, $sort = null);
‥‥
```

(2) /sample/tests/Services/Impl/Table2ServiceImpl.php修正
selectメソッドの引数修正
引数に`$limit = null, $page = null, $sort = null`追加

```Tests\Services\Impl\Table2ServiceImpl.php
‥‥
    public function select($id, $varchar_col, $int_col, $datetime_col, $date_col, $time_col, $limit = null, $page = null, $sort = null)
    {
    }
‥‥
```

(3) /sample/app/Services/Impl/Table2ServiceImpl.php修正
selectメソッド修正

```App\Services\Impl\Table2ServiceImpl.php
‥‥
    public function select($id, $varchar_col, $int_col, $datetime_col, $date_col, $time_col, $limit = null, $page = null, $sort = null)
    {

        $columns = [
            'table1.id            as table1_id',
            'table1.varchar_col   as table1_varchar_col',
            'table1.int_col       as table1_int_col',
            'table1.datetime_col  as table1_datetime_col',
            'table1.date_col      as table1_date_col',
            'table1.time_col      as table1_time_col',
            'table2.id            as table2_id',
            'table2.varchar_col   as table2_varchar_col',
            'table2.int_col       as table2_int_col',
            'table2.datetime_col  as table2_datetime_col',
            'table2.date_col      as table2_date_col',
            'table2.time_col      as table2_time_col',
            'table3.id            as table3_id',
            'table3.varchar_col   as table3_varchar_col',
            'table3.int_col       as table3_int_col',
            'table3.datetime_col  as table3_datetime_col',
            'table3.date_col      as table3_date_col',
            'table3.time_col      as table3_time_col',
        ];

        $db = DB::table('table2')
                    ->leftJoin('table1', 'table2.table1_id', '=', 'table1.id')
                    ->leftJoin('table3', 'table2.id', '=', 'table3.table2_id')
                    ->select($columns);

        if (!is_null($id)) {
            $db->where('table2.id', '=', $id);
        }
        if (!is_null($varchar_col)) {
            $db->where('table2.varchar_col', 'like', '%'. addcslashes($varchar_col, '\\_%') . '%');
        }
        if (!is_null($int_col)) {
            $db->where('table2.int_col', '=', $int_col);
        }
        if (!is_null($datetime_col)) {
            $db->where('table2.datetime_col', '=', $datetime_col);
        }
        if (!is_null($date_col)) {
            $db->where('table2.date_col', '=', $date_col);
        }
        if (!is_null($time_col)) {
            $db->where('table2.time_col', '=', $time_col);
        }

        if (!is_null($sort)) {
            $sortCol = ['table2.id',
                        'table2.varchar_col',
                        'table2.int_col',
                        'table2.datetime_col',
                        'table2.date_col',
                        'table2.time_col'];
            $db->orderBy($sortCol[$sort]);
        }

        if (!is_null($page) && !is_null($limit)) {
            $recordList = $db->paginate($limit, $columns, 'page', $page);
        } else {
            $recordList = $db->get();
        }

        return $recordList;

    }
‥‥
```
大きな修正は$db->paginateメソッドを追加したことです
paginateメソッドの
第1引数は1ページに表示する件数です。
第2引数はselectするカラムです。
今回は
`DB::table('table2')`
`  ‥‥`
`  ->select($columns);`
で取得するカラムを指定しているので意味ないです
第3引数はページ番号が送信されてくるHTTPリクエストパラメーター名です。
第4引数はページ番号です。select文ではこの引数-1がoffsetになります
第2引数から第4引数は省略できます。省略した場合、第2引数は[*]、第3引数は'page'、第4引数はnullになり、ページ番号は第3引数名のHTTPパラメーター値になります(第3引数、第4引数を省略すれば、pageという名前のHTTPパラメーター値となる)
今回はクエリビルダを使いましたが、paginateメソッドはEloquentでも使えます
[Laravelでデータベースを操作する(Eloquent編)](https://qiita.com/toontoon/items/97a49d1ea94e30bb8b48)
でEloquentを使うサービスクラスを作成しました。そこではgetメソッドをつかってselectしましたが、getメソッドの部分を今回と同じpaginateメソッドに変更すれば、Eloquentでページネーションできます。クエリビルダのpaginateメソッドもEloquentのpaginateメソッドも引数は同じです

# フォームリクエストの修正
ここで修正するフォームリクエストは
[Laravelでデータベースを操作する(クエリビルダ編)](https://qiita.com/toontoon/items/b38f6aa89316ef1ee4b7)
で作成したものです
 /sample/app/Http/Requests/Table2Request.php修正

```Table2Request.php
‥‥
    public function rules()
    {
‥‥
        return [
            'id'               => [$idRequire, 'numeric', 'max:18446744073709551615'],
            'table1_id'        => ['nullable', 'numeric', 'max:18446744073709551615'],
            'varchar_col'      => ['nullable', 'max:255'],
            'int_col'          => ['nullable', 'integer', 'max:2147483647'],
            'datetime_col'     => ['nullable', 'date_format:Y-m-d H:i:s'],
            'date_col'         => ['nullable', 'date_format:Y-m-d'],
            'time_col'         => ['nullable', 'date_format:H:i:s'],
            'limit'            => ['nullable', 'integer', 'min:1', 'max:200'],
            'page'             => ['nullable', 'integer', 'min:1', 'max:2147483647'],
            'sort'             => ['nullable', 'integer', 'min:0', 'max:5'],
        ];
    }
‥‥
```
limit、page、sortの入力制限を追加しました

# Controllerにメソッド追加
(1) /sample/app/Http/Controllers/SampleController.phpにpaginateQueryBuilderメソッドを追記
フォームリクエスト、サービスクラスのuse文は
[Laravelでデータベースを操作する(クエリビルダ編)](https://qiita.com/toontoon/items/b38f6aa89316ef1ee4b7)
で書きました

```SampleController.php
    public function paginateQueryBuilder(Table2Request $request, Table2Service $table2Service)
    {
        if (is_null($request->session()->get('errors'))) {
            $request->flash();
        }

        $id = $request->input('id');
        $varchar_col = $request->input('varchar_col');
        $int_col =  $request->input('int_col');
        $datetime_col =  $request->input('datetime_col');
        $date_col =  $request->input('date_col');
        $time_col =  $request->input('time_col');
        $limit =  $request->input('limit') ?? 5;
        $page =  $request->input('page') ?? 1;
        $sort =  $request->input('sort') ?? 0;

        $recordList = null;
        if (is_null($request->session()->get('errors'))) {
            $recordList = $table2Service->select($id, $varchar_col, $int_col, $datetime_col, $date_col, $time_col, $limit, $page, $sort);
        }

        $data = [
            'recordList'   => $recordList,
            'id'           => $id,
            'varchar_col'  => $varchar_col,
            'int_col'      => $int_col,
            'datetime_col' => $datetime_col,
            'date_col'     => $date_col,
            'time_col'     => $time_col,
            'limit'        => $limit,
            'page'         => $page,
            'sort'         => $sort
        ];

        return view('sample.paginate', $data);
    }
```

(2) /sample/routes/web.phpに下記を追記
`Route::get('sample/paginate-query-builder', 'SampleController@paginateQueryBuilder');`

# viewの作成
(1) ページネーションビューの作成
ページネーションビューとは前のページへリンク、次のページへリンク、ページ番号リンクを描画するビューです
コマンドラインで 
`cd sample`
`php artisan vendor:publish --tag=laravel-pagination`
xdebugの設定をしているとeclipseが実行していいですかというプロンプトを出すのでOKを押します
eclipseプロジェクトを右クリック→リフレッシュ
resources/views/vendor/paginationフォルダが現れます。そのフォルダの中に5つファイルができています
その中のbootstrap-4.blade.phpが今回使うページネーションビューになります。
ページネーションビューを独自のデザインにする場合、bootstrap-4.blade.phpを修正してください
今回修正したサービスクラスで使ったpaginateメソッドの戻り値(LengthAwarePaginatorクラスのインスタンス)がデフォルトで使用するページネーションビューがpagination::bootstrap-4です。
LengthAwarePaginator::defaultViewメソッドでデフォルトビューを変えることができます。また、デフォルトビューを変えなくても、後程使うlinksメソッドの第1引数にビュー名を渡すことでもカスタムページネーションビューを使用できます

(2) /sample/resources/views/sample/paginate.blade.phpファイル作成

```paginate.blade.php
<html>
    <head>
        <title>sample</title>
        <style>
        .sample-table {
            border-collapse:collapse;
            white-space: nowrap;
            border: 1px solid #000000;
        }
        .sample-table thead {
            background-color: #33CCFF;
            color: #FFFFFF;
            font-weight: bold;
        }
        .sample-table td {
            padding-left:10px;
            padding-right:10px;
            border: 1px solid #000000;
        }
        .pagination li {
            display: inline-block;
        }
        </style>
    </head>
    <body>

        <form action="{{ url('sample/paginate-query-builder') }}" method="get">
            @error('id')
                @foreach ($errors->get('id') as $error)
                    <div style="color:red;">{{ $error }}</div>
                @endforeach
            @enderror
            <div>id<input type="text" name="id" value="{{ old('id') }}"></div>
            @error('varchar_col')
                @foreach ($errors->get('varchar_col') as $error)
                    <div style="color:red;">{{ $error }}</div>
                @endforeach
            @enderror
            <div>varchar_col<input type="text" name="varchar_col" value="{{ old('varchar_col') }}"></div>
            @error('int_col')
                @foreach ($errors->get('int_col') as $error)
                    <div style="color:red;">{{ $error }}</div>
                @endforeach
            @enderror
            <div>int_col<input type="text" name="int_col" value="{{ old('int_col') }}"></div>
            @error('datetime_col')
                @foreach ($errors->get('datetime_col') as $error)
                    <div style="color:red;">{{ $error }}</div>
                @endforeach
            @enderror
            <div>datetime_col<input type="text" name="datetime_col" value="{{ old('datetime_col') }}"></div>
            @error('date_col')
                @foreach ($errors->get('date_col') as $error)
                    <div style="color:red;">{{ $error }}</div>
                @endforeach
            @enderror
            <div>date_col<input type="text" name="date_col" value="{{ old('date_col') }}"></div>
            @error('time_col')
                @foreach ($errors->get('time_col') as $error)
                    <div style="color:red;">{{ $error }}</div>
                @endforeach
            @enderror
            <div>time_col<input type="text" name="time_col" value="{{ old('time_col') }}"></div>
            @error('limit')
                @foreach ($errors->get('limit') as $error)
                    <div style="color:red;">{{ $error }}</div>
                @endforeach
            @enderror
            <div>1ページ表示件数<input type="text" name="limit" value="{{ old('limit', 5) }}"></div>
            <input type="submit" >
        </form>

        <br>

        @isset($recordList)
            @if ($recordList->total() === 0)
            	<div>
            	    検索結果は0件です
            	</div>
            @else
                <table class="sample-table">
                    <thead>
                        <tr>
                            <td colspan=6>table1</td>
                            <td colspan=6>table2</td>
                            <td colspan=6>table3</td>
                        </tr>
                        <tr>
                            <td>id</td>
                            <td>varchar_col</td>
                            <td>int_col</td>
                            <td>datetime_col</td>
                            <td>date_col</td>
                            <td>time_col</td>
                            <td><a href="{{ $recordList->appends([
                                            						'id'           => $id,
                                                                    'varchar_col'  => $varchar_col,
                                                                    'int_col'      => $int_col,
                                                                    'datetime_col' => $datetime_col,
                                                                    'date_col'     => $date_col,
                                                                    'time_col'     => $time_col,
                                                                    'limit'        => $limit,
                                                                    'page'         => $page,
                                                                    'sort'         => 0])
                                                       ->url($recordList->currentPage()) }}" >id</a></td>
                            <td><a href="{{ $recordList->appends([
                                            						'id'           => $id,
                                                                    'varchar_col'  => $varchar_col,
                                                                    'int_col'      => $int_col,
                                                                    'datetime_col' => $datetime_col,
                                                                    'date_col'     => $date_col,
                                                                    'time_col'     => $time_col,
                                                                    'limit'        => $limit,
                                                                    'page'         => $page,
                                                                    'sort'         => 1])
                                                       ->url($recordList->currentPage()) }}" >varchar_col</a></td>
                            <td><a href="{{ $recordList->appends([
                                            						'id'           => $id,
                                                                    'varchar_col'  => $varchar_col,
                                                                    'int_col'      => $int_col,
                                                                    'datetime_col' => $datetime_col,
                                                                    'date_col'     => $date_col,
                                                                    'time_col'     => $time_col,
                                                                    'limit'        => $limit,
                                                                    'page'         => $page,
                                                                    'sort'         => 2])
                                                       ->url($recordList->currentPage()) }}" >int_col</a></td>
                            <td><a href="{{ $recordList->appends([
                                            						'id'           => $id,
                                                                    'varchar_col'  => $varchar_col,
                                                                    'int_col'      => $int_col,
                                                                    'datetime_col' => $datetime_col,
                                                                    'date_col'     => $date_col,
                                                                    'time_col'     => $time_col,
                                                                    'limit'        => $limit,
                                                                    'page'         => $page,
                                                                    'sort'         => 3])
                                                       ->url($recordList->currentPage()) }}" >datetime_col</a></td>
                            <td><a href="{{ $recordList->appends([
                                            						'id'           => $id,
                                                                    'varchar_col'  => $varchar_col,
                                                                    'int_col'      => $int_col,
                                                                    'datetime_col' => $datetime_col,
                                                                    'date_col'     => $date_col,
                                                                    'time_col'     => $time_col,
                                                                    'limit'        => $limit,
                                                                    'page'         => $page,
                                                                    'sort'         => 4])
                                                       ->url($recordList->currentPage()) }}" >date_col</a></td>
                            <td><a href="{{ $recordList->appends([
                                            						'id'           => $id,
                                                                    'varchar_col'  => $varchar_col,
                                                                    'int_col'      => $int_col,
                                                                    'datetime_col' => $datetime_col,
                                                                    'date_col'     => $date_col,
                                                                    'time_col'     => $time_col,
                                                                    'limit'        => $limit,
                                                                    'page'         => $page,
                                                                    'sort'         => 5])
                                                       ->url($recordList->currentPage()) }}" >time_col</a></td>
                            <td>id</td>
                            <td>varchar_col</td>
                            <td>int_col</td>
                            <td>datetime_col</td>
                            <td>date_col</td>
                            <td>time_col</td>
                        </tr>
                    </thead>
                    <tbody>
                            @foreach ($recordList as $record)
                                <tr>
                                    <td>{{ $record->table1_id }}</td>
                                    <td>{{ $record->table1_varchar_col }}</td>
                                    <td>{{ $record->table1_int_col }}</td>
                                    <td>{{ $record->table1_datetime_col }}</td>
                                    <td>{{ $record->table1_date_col }}</td>
                                    <td>{{ $record->table1_time_col }}</td>
                                    <td>{{ $record->table2_id }}</td>
                                    <td>{{ $record->table2_varchar_col }}</td>
                                    <td>{{ $record->table2_int_col }}</td>
                                    <td>{{ $record->table2_datetime_col }}</td>
                                    <td>{{ $record->table2_date_col }}</td>
                                    <td>{{ $record->table2_time_col }}</td>
                                    <td>{{ $record->table3_id }}</td>
                                    <td>{{ $record->table3_varchar_col }}</td>
                                    <td>{{ $record->table3_int_col }}</td>
                                    <td>{{ $record->table3_datetime_col }}</td>
                                    <td>{{ $record->table3_date_col }}</td>
                                    <td>{{ $record->table3_time_col }}</td>
                                </tr>
                            @endforeach
                    </tbody>
                </table>
            @endif
        @endisset

        @isset($recordList)
            {{ $recordList->appends([
            						'id'           => $id,
                                    'varchar_col'  => $varchar_col,
                                    'int_col'      => $int_col,
                                    'datetime_col' => $datetime_col,
                                    'date_col'     => $date_col,
                                    'time_col'     => $time_col,
                                    'limit'        => $limit,
                                    'page'         => $page,
                                    'sort'         => $sort
                                    ])
                          ->links() }}
        @endisset

    </body>
</html>
```
thead->tr->tdタグ内のaタグのhref属性では`$recordList->appends()->url()`を実行してます。`$recordList`はLengthAwarePaginatorクラスのインスタンスです。
`$recordList->currentPage()`は現在のページ番号を返します
url($recordList->currentPage())は`$recordList->currentPage()`ページのURLを返します。
`$recordList->appends`はurl()が返すURLに付加するクエリ文字列です
よく見ていただくと、appendsメソッドに渡している配列のsort要素の値を変えています

paginate.blade.php下部に書いた`$recordList->appends()->links()`は先ほど生成したbootstrap-4.blade.phpを描画します
linksメソッドの第1引数にビュー名を渡すことでbootstrap-4.blade.phpではないカスタムページネーションビューを描画することもできます
appendsメソッドは先ほど説明した通りページリンクURLに付加するクエリ文字列です

# 動作確認
`http://localhost/laravelSample/sample/paginate-query-builder`
にアクセスすると画面下部にページリンクが描画されています

# POSTに対応してみる
今作成したものはGETメソッドに対応したものでした。
多くの場合、検索はGETメソッドで大丈夫でしょうが、
検索条件がたくさん設置され、長い文字列カラムが検索条件に入っている場合など、GETメソッドにした場合、URLの文字列制限に引っかかり、URLのクエリ文字列が途中で切れてしまうこともあります
そのような事情で検索をPOSTメソッドで実装した場合に対応してみましょう

(1) まず、resources/views/sample/paginate.blade.phpのformタグのmethod属性値をpostに変え、sample/routes/web.phpに定義した'sample/paginate-query-builder'のルーティングをRoute::getからRoute::match(['get', 'post']に変更します(今回は動作確認でsample/paginate-query-builderに直リンクするためgetも許容しておく)

(2) resources/views/vendor/pagination/bootstrap-4.blade.php修正
`@if ($paginator->hasPages())`直下(2行目)に下記を追記

```
    @php
        $___paginatorHiddenFunc = function ($name, $value) use (&$___paginatorHiddenFunc) {
            if (is_array($value)) {
                foreach ($value as $k => $v) {
                    $___paginatorHiddenFunc($name . '[' . $k . ']', $v);
                }
            } else {
                echo '<input type="hidden" name="' . htmlspecialchars($name, ENT_QUOTES) . '" value="' . htmlspecialchars($value, ENT_QUOTES) . '">';
            }
        };
        $___paginatorQueryList = null;
        parse_str(parse_url($paginator->url(1), PHP_URL_QUERY), $___paginatorQueryList);
    @endphp
```
やっていることは2つです
type属性値がhiddenのinputタグを出力するメソッドを定義している(`$___paginatorHiddenFunc`)
1ページ目のページリンクのクエリ文字列を配列として取得している(`$___paginatorQueryList`)

bootstrap-4.blade.phpにはaタグが3つ書いてあります
前のページへリンク、ページ番号リンク、次のページへリンクです
これらをformタグに直します

前のページへリンク

```
<form action="{{ $paginator->path() }}" method="post">
    @csrf
    @foreach ($___paginatorQueryList as $___paginatorQueryName => $___paginatorQueryValue)
        @if ($___paginatorQueryName === $paginator->getPageName())
            @php $___paginatorHiddenFunc($___paginatorQueryName, $paginator->currentPage() - 1); @endphp
        @elseif ($___paginatorQueryName !== '_token')
            @php $___paginatorHiddenFunc($___paginatorQueryName, $___paginatorQueryValue); @endphp
        @endif
    @endforeach
    <input type="submit" value="&lsaquo;">
</form>
```

ページ番号リンク

```
<form action="{{ $paginator->path() }}" method="post">
    @csrf
    @foreach ($___paginatorQueryList as $___paginatorQueryName => $___paginatorQueryValue)
        @if ($___paginatorQueryName === $paginator->getPageName())
            @php $___paginatorHiddenFunc($___paginatorQueryName, $page); @endphp
        @elseif ($___paginatorQueryName !== '_token')
            @php $___paginatorHiddenFunc($___paginatorQueryName, $___paginatorQueryValue); @endphp
        @endif
    @endforeach
    <input type="submit" value="{{$page}}">
</form>
```

次のページへリンク

```
<form action="{{ $paginator->path() }}" method="post">
    @csrf
    @foreach ($___paginatorQueryList as $___paginatorQueryName => $___paginatorQueryValue)
        @if ($___paginatorQueryName === $paginator->getPageName())
            @php $___paginatorHiddenFunc($___paginatorQueryName, $paginator->currentPage() + 1); @endphp
        @elseif ($___paginatorQueryName !== '_token')
            @php $___paginatorHiddenFunc($___paginatorQueryName, $___paginatorQueryValue); @endphp
        @endif
    @endforeach
    <input type="submit" value="&rsaquo;">
</form>
```

やっていることは
配列として取得した1ページ目のページリンクのクエリ文字列(`$___paginatorQueryList`)をループし
type属性値がhiddenのinputタグを出力するメソッド(`$___paginatorHiddenFunc`)を実行しているだけです

(3) resources/views/sample/paginate.blade.phpファイル作成
thead->tr->tdタグ内のaタグをformタグに変更します

```
<form action="{{ $recordList->path() }}" method="post">
 @csrf
 <input type="hidden" name="id" value="{{ $id }}">
 <input type="hidden" name="varchar_col" value="{{ $varchar_col }}">
 <input type="hidden" name="int_col" value="{{ $int_col }}">
 <input type="hidden" name="datetime_col" value="{{ $datetime_col }}">
 <input type="hidden" name="date_col" value="{{ $date_col }}">
 <input type="hidden" name="time_col" value="{{ $time_col }}">
 <input type="hidden" name="limit" value="{{ $limit }}">
 <input type="hidden" name="sort" value="0">{{-- ここは各カラムの番号にする --}}
 <input type="hidden" name="page" value="{{ $page }}">
 <input type="submit" value="id">{{-- ここは各カラム名にする --}}
</form>

```

# 動作確認
`http://localhost/laravelSample/sample/paginate-query-builder`
にアクセスします
ページリンクをクリックするとPOST送信できました
見た目は自由にCSSで変えてください