Использование пагинации совместно с API ресурсами

06 Ноября 2022 20:48

Как известно, слой API Resources используется для настройки вывода данных по API. И в случае с обычным получением данных, мы могли бы сделать что-то вроде следующего:

Файл app\Http\Resources\ArticleCollection.php

<?php

namespace App\Http\Resources;

use Illuminate\Http\Resources\Json\ResourceCollection;

class ArticleCollection extends ResourceCollection
{
    public static $wrap = '';

    public function toArray($request): array
    {
        return [
            'articles' => $this->collection,
            'articlesCount' => $this->count()
        ];
    }
}

Файл app\Http\Controllers\ArticleController.php

<?php

namespace App\Http\Controllers;

use App\Http\Resources\ArticleCollection;
use App\Models\Article;

class ArticleController extends Controller
{
    public function index(): array
    {
        return new ArticleCollection(Article::all());
    }
}

И это будет нормально работать т.к. в ArticleCollection попадает предусмотренная для этого коллекция со статьями которая, в свою очередь, подставляется в обертку $wrap и выводится как json.
Но в случаях с paginate, будет немного иначе. Например:

<?php

namespace App\Http\Controllers;

use App\Http\Resources\ArticleCollection;
use App\Models\Article;

class ArticleController extends Controller
{
    public function index(): array
    {
        return new ArticleCollection(Article::cursorPaginate(5));
    }
}

Вывод получится вот таким:

Как видно, пустая обертка $wrap проигнорирована и добавлена в общий вывод с пустым ключом. Но чтобы использовать удобный нам результирующий набор данных, можно использовать зарезервированный метод withResponse() в коллекции ArticleCollection. Изначально, он ничего не делает, но он выполняется после метода toArray() и с помощью него можно скорректировать вывод.

Решение

<?php

namespace App\Http\Resources;

use Illuminate\Http\Resources\Json\ResourceCollection;

class ArticleCollection extends ResourceCollection
{
    public static $wrap = 'data';

    public function withResponse($request, $response)
    {
        $arrResponse = json_decode($response->getContent(), true);

        unset($arrResponse['links'], $arrResponse['meta']);

        $response->setContent(json_encode($arrResponse['data']));
    }

    public function toArray($request): array
    {
        return [
            'articles' => $this->collection,
            'count' => $this->count(),
            'pagination' => [
                'per_page' => $this->perPage(),
                'next_page_url' => $this->nextPageUrl(),
                'prev_page_url' => $this->previousPageUrl(),
            ]
        ];
    }
}

Что сделали: добавили $wrap = 'data';, добавили метод withResponse(), в нем выпилили links и meta и записали в контент ответа то, что в data.

В toArray() составили тот ответ для API, который нужен. Методы пагинации типа nextPage, perPage и т.д. смотреть в доках:
https://laravel.com/docs/9.x/pagination#cursor-paginator-instance-methods