Repository navigation
Replies: 2 comments 1 reply
|
1 reply
|
I ran into the same issue with
Copy these files into use App\Support\Macros\JsonApiCursorPaginateMacro;
use App\Support\Macros\JsonApiPaginateMacro;
use Illuminate\Database\Eloquent\Builder as EloquentBuilder;
use Illuminate\Database\Query\Builder as QueryBuilder;
/**
* Bootstrap application services.
*/
public function boot(): void
{
foreach ([EloquentBuilder::class, QueryBuilder::class] as $builder) {
$builder::macro('jsonApiPaginate', (new JsonApiPaginateMacro())());
$builder::macro('jsonApiCursorPaginate', (new JsonApiCursorPaginateMacro())());
}
}Then I use them like this: use App\Models\User;
// ?page[size]=20&page[number]=2
$users = User::query()->jsonApiPaginate();
// ?page[size]=20&page[cursor]=...
$users = User::query()->orderBy('id')->jsonApiCursorPaginate();The macros read the nested pagination parameters and preserve Note: No maximum page size is enforced, matching Laravel's default pagination behavior. AI disclosure: I wrote this answer with AI assistance based on the source code. AI wrote the unit tests for the macros entirely. FilesJsonApiPaginateMacro.php<?php
declare(strict_types=1);
namespace App\Support\Macros;
use Closure;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Pagination\LengthAwarePaginator;
/**
* @mixin \Illuminate\Database\Eloquent\Builder<\Illuminate\Database\Eloquent\Model>
* @mixin \Illuminate\Database\Query\Builder
*/
class JsonApiPaginateMacro
{
/**
* Create the JSON:API pagination macro closure.
*
* @return Closure(?int, array<int, string>, string, ?int, Closure|int|null): LengthAwarePaginator<int, Model>
*
* @noinspection PhpVariableIsUsedOnlyInClosureInspection
*/
public function __invoke(): Closure
{
$macro = $this;
/**
* @param list<string> $columns
* @return LengthAwarePaginator<int, Model>
*/
return function (
?int $perPage = null,
array $columns = ['*'],
string $pageName = 'page',
?int $page = null,
Closure|int|null $total = null
) use ($macro): LengthAwarePaginator {
$pageSize = $macro->pageSizeParameter($pageName);
/** @var array<int, string> $columns */
$paginator = $this->paginate(
$perPage ?? $pageSize,
$columns,
$pageName . '[number]',
$page ?? $macro->pageNumberParameter($pageName),
$total,
);
if ($pageSize !== null) {
return $paginator->appends([$pageName . '[size]' => $pageSize]);
}
return $paginator;
};
}
/**
* Read a valid JSON:API page size query parameter.
*/
public function pageSizeParameter(string $pageName = 'page'): ?int
{
$pageSize = $this->pageParameter('size', $pageName);
if (filter_var($pageSize, FILTER_VALIDATE_INT) === false) {
return null;
}
$pageSize = (int) $pageSize;
if ($pageSize < 1) {
return null;
}
return $pageSize;
}
/**
* Read a valid JSON:API page number query parameter.
*/
public function pageNumberParameter(string $pageName = 'page'): ?int
{
$pageNumber = $this->pageParameter('number', $pageName);
if (filter_var($pageNumber, FILTER_VALIDATE_INT) === false) {
return null;
}
return (int) $pageNumber;
}
/**
* Read a JSON:API page query parameter.
*/
public function pageParameter(string $key, string $pageName = 'page'): int|string|null
{
$pageParameters = request()->query($pageName, []);
if (! is_array($pageParameters)) {
return null;
}
$value = $pageParameters[$key] ?? null;
if (is_int($value) || is_string($value)) {
return $value;
}
return null;
}
}JsonApiCursorPaginateMacro.php<?php
declare(strict_types=1);
namespace App\Support\Macros;
use Closure;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Pagination\Cursor;
use Illuminate\Pagination\CursorPaginator;
/**
* @mixin \Illuminate\Database\Eloquent\Builder<\Illuminate\Database\Eloquent\Model>
* @mixin \Illuminate\Database\Query\Builder
*/
class JsonApiCursorPaginateMacro
{
/**
* Create the JSON:API cursor pagination macro closure.
*
* @return Closure(?int, array<int, string>, string, Cursor|string|null): CursorPaginator<int, Model>
*
* @noinspection PhpVariableIsUsedOnlyInClosureInspection
*/
public function __invoke(): Closure
{
$macro = $this;
/**
* @param list<string> $columns
* @return CursorPaginator<int, Model>
*/
return function (
?int $perPage = null,
array $columns = ['*'],
string $pageName = 'page',
Cursor|string|null $cursor = null,
) use ($macro): CursorPaginator {
$pageSize = $macro->pageSizeParameter($pageName);
/** @var array<int, string> $columns */
$paginator = $this->cursorPaginate(
$perPage ?? $pageSize,
$columns,
$pageName . '[cursor]',
$cursor ?? $macro->cursorParameter($pageName),
);
if ($pageSize !== null) {
return $paginator->appends([$pageName . '[size]' => $pageSize]);
}
return $paginator;
};
}
/**
* Read a valid JSON:API page size query parameter.
*/
public function pageSizeParameter(string $pageName = 'page'): ?int
{
$pageSize = $this->pageParameter('size', $pageName);
if (filter_var($pageSize, FILTER_VALIDATE_INT) === false) {
return null;
}
$pageSize = (int) $pageSize;
if ($pageSize < 1) {
return null;
}
return $pageSize;
}
/**
* Read a JSON:API cursor query parameter.
*/
public function cursorParameter(string $pageName = 'page'): ?string
{
$cursor = $this->pageParameter('cursor', $pageName);
if (is_string($cursor)) {
return $cursor;
}
return null;
}
/**
* Read a JSON:API page query parameter.
*/
public function pageParameter(string $key, string $pageName = 'page'): int|string|null
{
$pageParameters = request()->query($pageName, []);
if (! is_array($pageParameters)) {
return null;
}
$value = $pageParameters[$key] ?? null;
if (is_int($value) || is_string($value)) {
return $value;
}
return null;
}
} |
0 replies
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Uh oh!
There was an error while loading. Please reload this page.
In Paginator, currently
pageNamecan only be a "first-level" query parameter. Some Spechs (i.e. JSON:API) require pagination query parameters to reside under or within thepageparameter. This might look like?page[size]=5&page[number]=3orpage[cursor]=123. Currently this seems not to be possible with Laravel.Using the string
'page[number]'as pageName results in a query string with doubled parameterpage%5Bnumber%5D.The reason for this lies in
Illuminate\Pagination\AbstractPaginator::url()where $parameters is filled with:Neither
Arr::query()norhttp_build_query()handle this conflicting situation properly.A simple solution would be, to normalize / flatten the $parameters array before
array_merge()in theurl()method. Not sure if this would cause any other issues.All reactions