Edited at

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);
}
}


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

要調整、動作未確認。