joram
2 years ago
578
7
Laravel

Pagination using JSON API strategy.

Posted 2 years ago by joram

I'm trying to implement a JSON API pagination strategy using the Laravel paginator. I'd like to use the query string parameters page[number] and page[size] to request specific page numbers and page size as suggested in the JSON API spec.

The paginator will get the page number from the request when the paginators pageName option is specified using the array dot notation (page.number) but it won't set it correctly in the generated URLs.

Is there any way to get the paginator to generate URLs in the form of page[number]=2?

Steps To Reproduce:

Consider the following trait:

use Illuminate\Contracts\Pagination\LengthAwarePaginator;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Http\Request;

trait PaginatesResources
{
    protected $pageSizeDefault = 10;

    protected $pageNumberKey = 'page.number';
    protected $pageSizeKey = 'page.size';

    /**
     * @param Builder $query
     * @return LengthAwarePaginator
     */
    protected function paginate(Builder $query): LengthAwarePaginator
    {
        /** @var Request $request */
        $request = app('request');

        $size = $request->input($this->pageSizeKey, $this->pageSizeDefault);

        $paginator = $query->paginate($size, ['*'], $this->pageNumberKey);
        $paginator->appends(array_except($request->input(), $this->pageNumberKey));

        return $paginator;
    }
}

This resolves the current page correctly from URLs like resource?page[number]=2, but generates URLs in the form of resource?page.number=2 instead of the desired resource?page[number]=2.

To work around this, it's possible to modify the initialization to:

$paginator = $query->paginate($size, ['*'], $this->pageNumberKey);
$paginator->setPageName('page[number]');
// or use: preg_replace("/(.*)\.(.*)/", "$1[$2]", $this->pageNumberKey);
$paginator->appends(array_except($request->input(), $this->pageNumberKey));

Feels a bit hacky and would duplicate the page key definition in the trait (or the use of some extra processing to transform the dot notation to square brackets notation).

The following code in Illuminate\Pagination\AbstractPaginator is responsible for generating the URLs:

public function url($page)
{
    // [...]
    $parameters = [$this->pageName => $page];

    if (count($this->query) > 0) {
        $parameters = array_merge($this->query, $parameters);
    }
    // [...]
}

Replacing this code chunk with the following solves the issue for this use-case, but I'm not certain of any side-effects:

$parameters = $this->query;

array_set($parameters, $this->pageName, $page);

Anyone else bumped into this? Would this be an improvement in the pagination component or should this be solved in app code like above?

Note: cross-posted from https://github.com/laravel/framework/issues/19167

Please sign in or create an account to participate in this conversation.