

Laravelを使ってページネーションする場合  
paginate()メソッドを使うのですが毎回  
件数を取得するSQLと結果を取得するSQLの二つが実行されます。  
paginate()メソッドはどんな仕組みになっているのでしょうか？


CC BY-SA 4.0
2018-05-20T12:08:14.643
44105
2018-05-31T07:01:11.683
25778
question
php laravel
Laravelを使ったページネーション


<https://github.com/laravel/framework/blob/eddc5a1995e697f0d9fa703ca03776fdcee96c78/src/Illuminate/Database/Query/Builder.php#L1922>

[code]

    public function paginate($perPage = 15, $columns = ['*'], $pageName = 'page', $page = null)
    {
        $page = $page ?: Paginator::resolveCurrentPage($pageName);
        $total = $this->getCountForPagination($columns);
        $results = $total ? $this->forPage($page, $perPage)->get($columns) : collect();
        return $this->paginator($results, $total, $perPage, $page, [
            'path' => Paginator::resolveCurrentPath(),
            'pageName' => $pageName,
        ]);
    }
    
[/code]

まず、全レコード数`$total`を取得しています(件数を取得するSQL)。  
1ページ当たりの行数`$perPage`が決まっているので、全ページ数が分かります。  
今回表示しようとしているページ番号から、取得したい行が分かり、ページに表示するレコード一覧を取得しています。（ちょっと斜め読みしただけなので不正確かもしれません）

この仕組みはページネーションを実現しているフレームワークは共通しているはずです。


CC BY-SA 4.0
2018-05-21T06:20:04.897
44123
2018-05-21T06:20:04.897
2238
44105
answer


@htb さんが回答されているとおり，基本的にはリクエストごとに毎回結構負荷のかかる処理が走っちゃいますね。

  * 件数の取得: `paginate` のみ
  * データの取得: `paginate` `simplePaginate` 共通

`LIMIT ... OFFSET ...`
クエリを毛嫌いしているので，カーソルベースのページネーションを実現するライブラリを自作しました。弊社業務でも採用しているのでよかったら使ってみてください。

  * [OFFSETを使わない高速ページネーションを任意のPHPフレームワークで超簡単に実現する - Qiita](https://qiita.com/mpyw/items/b94b7d69146777f7a407)
  * [lampager/lampager: Rapid pagination for various PHP frameworks](https://github.com/lampager/lampager)
  * [lampager/lampager-laravel: Rapid pagination for Laravel](https://github.com/lampager/lampager-laravel)

注意点:
Laravel標準のページネーションのように，`Request`オブジェクトに対して強引にグローバルアクセスをしているわけではないので，コントローラにて自分でパラメータ等を渡してあげる必要はあります。

実際の使用例↓

（IDEで補完を利かせるためにあえてマクロは使用しておりません）

### `PostController.php`

[code]

    <?php
    
    declare(strict_types=1);
    
    namespace App\Http\Controllers;
    
    use App\Post;
    use Illuminate\Http\Request;
    use Lampager\Laravel\PaginationResult;
    use Lampager\Laravel\Paginator;
    
    class PostController extends Controller
    {
        /**
         * @param  Request          $request
         * @return PaginationResult
         */
        public function index(Request $request): PaginationResult
        {
            $query = Post::query();
    
            if ($types = array_intersect(config('app.post_types'), explode(',', $request->input('type', '')))) {
                $query->whereIn('type', $types);
            }
    
            return (new Paginator($query))
                    ->orderByDesc('updated_at')
                    ->orderByDesc('id')
                    ->limit(20)
                    ->paginate($this->replaceParameterNames($request->only('next_updated_at', 'next_id'), [
                        'next_updated_at' => 'updated_at',
                        'next_id' => 'id',
                    ]));
        }
    }
    
[/code]

### `ReplacesQueryParameterNames.php`

基底 `Controller` でミックスインしておきます。

[code]

    <?php
    
    declare(strict_types=1);
    
    namespace App\Http\Controllers\Concerns;
    
    /**
     * Trait ReplacesQueryParameterNames
     */
    trait ReplacesQueryParameterNames
    {
        /**
         * 引数のキーを置換して返します。
         *
         * @param  array $input
         * @param  array $map
         * @return array
         */
        protected function replaceParameterNames(array $input, array $map): array
        {
            $output = [];
            foreach ($input as $key => $value) {
                $output[$map[$key] ?? $key] = $value;
            }
            return $output;
        }
    }
    
[/code]


CC BY-SA 4.0
2018-05-31T07:01:11.683
44425
2018-05-31T07:01:11.683
940
44105
answer