PHP
laravel
pagination

Laravelでpagination用Link headerに対応する

  • 初版:2018.12.7
  • laravel-5.7, php-7.2

APIなどでページングデータを返却jsonに含めずヘッダに載せる場合はLinkを使うことが多い。その対応。

Responseマクロを書いて返却データを修正する

app/Providers/AppServiceProvider.php
<?php

use Illuminate\Support\ServiceProvider;

use Illuminate\Http\Resources\Json\ResourceCollection;
use Illuminate\Pagination\LengthAwarePaginator;
use Illuminate\Support\Facades\Response;

class AppServiceProvider extends ServiceProvider
{
    public function boot()
    {
        Response::macro('jsonpage', function ($coll) {
            $linkUrls = [];
            // Laravelに沿った開発をしている場合
            if ($coll instanceof ResourceCollection && $paginated = $coll->toArray()) {
                // Illuminate\Http\Resources\Json\ResourceCollectionを継承したコレクション(*1)に
                // paginateなコレクション(*2)を渡してる場合
                // *1 https://readouble.com/laravel/5.7/ja/eloquent-resources.html#pagination
                // *2 https://readouble.com/laravel/5.7/ja/pagination.html
                if (isset($paginated['first_page_url'])) {
                    $linkUrls = [
                        'first' => $paginated['first_page_url'] ?? null,
                        'last' => $paginated['last_page_url'] ?? null,
                        'prev' => $paginated['prev_page_url'] ?? null,
                        'next' => $paginated['next_page_url'] ?? null,
                    ];
                    // Linkヘッダ使うんだしwrappingははずしたい
                    $coll->withoutWrapping();
                }
            }

            // custom実装(案)
            if (empty($linkUrls)) {
                switch ($this->pattern) {
                    // Laravelパジネーションを使う場合こんな感じ
                    // https://github.com/laravel/framework/blob/9f313ce9bb5ad49a06ae78d33fbdd1c92a0e21f6/src/Illuminate/Pagination/LengthAwarePaginator.php#L41
                    case 'laravel':
                        $total = 1000;
                        $perPage = 10;
                        $currentPage = 2;
                        $options = [];
                        $pagination = new LengthAwarePaginator($coll, $total, $perPage, $currentPage, $options);
                        $paginated = $pagination->toArray();
                        if (isset($paginated['first_page_url'])) {
                            $linkUrls = [
                                'first' => $paginated['first_page_url'] ?? null,
                                'last' => $paginated['last_page_url'] ?? null,
                                'prev' => $paginated['prev_page_url'] ?? null,
                                'next' => $paginated['next_page_url'] ?? null,
                            ];
                        }
                        break;

                    // コレクションクラスで実装しとくとか
                    case 'original':
                        $linkUrls = $coll->getPaginatedLinks();
                        break;

                    // URLクエリだけ取得してここで組み立てるとか
                    case 'original2':
                        $url = request()->url(); // queryなし
                        $queries = $coll->getPaginationQuery();
                        // ...
                        break;
                }
            }

            // 追加レスポンスヘッダ
            $headers = [];

            // linksをレスポンスヘッダ用フォーマット
            $linkFormat = '<%s>; rel="%s"';
            $links = [];
            foreach ($linkUrls as $rel => $url) {
                if (!empty($url)) {
                    $links[] = sprintf($linkFormat, $url, $rel);
                }
            }
            if (!empty($links)) {
                $headers['Link'] = join(',', $links);
            }

            // total件数を追加する必要があれば、コレクションに件数取得するメソッド作って対応したり
            $headers['MyApp-Total-Count'] = $coll->getCustomTotal();

            return Response::json($coll, 200, $headers);
        });
    }
}

AppServiceProviderが肥大するので、専用のServiceProviderを作っても良さそう。

使うときは

app/Controllers/SomeController.php
<?php
namespace App\Http\Controllers;

class SomeController extends Controller
{
    public function index()
    {
        $list = NankanoCollection::list();
        response()->jsonpage($list);
    }
}

の様にマクロ登録した名前をメソッドにして呼び出す。

要調整、動作未確認。