Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Cursor pagination #8

Draft
wants to merge 4 commits into
base: develop
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<?php

declare(strict_types=1);

namespace Undabot\JsonApi\Definition\Exception\Request;

use Throwable;
use Undabot\JsonApi\Implementation\Model\Source\Source;

class InvalidParameterValueException extends RequestException
{
public function __construct(
private Source $source,
string $message,
$code = 0,
Throwable $previous = null
) {
parent::__construct($message, $code, $previous);
}

public function source(): Source
{
return $this->source;
}
}
23 changes: 23 additions & 0 deletions src/Definition/Exception/Request/MaxPageSizeExceededException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<?php

declare(strict_types=1);

namespace Undabot\JsonApi\Definition\Exception\Request;

use Throwable;

class MaxPageSizeExceededException extends RequestException
{
public function __construct(
int $requestedPageSize,
int $maxPageSize,
$code = 0,
Throwable $previous = null
) {
parent::__construct(
sprintf('You requested a size of %d, but %d is the maximum.', $requestedPageSize, $maxPageSize),
$code,
$previous,
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<?php

declare(strict_types=1);

namespace Undabot\JsonApi\Definition\Exception\Request;

use Throwable;

class RangePaginationNotSupportedException extends RequestException
{
public function __construct(
$code = 0,
Throwable $previous = null
) {
parent::__construct(
'Range pagination not supported',
$code,
$previous,
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,8 @@ interface PaginationInterface
public function getSize(): int;

public function getOffset(): int;

public function getAfter(): ?string;

public function getBefore(): ?string;
}
97 changes: 56 additions & 41 deletions src/Implementation/Factory/PaginationFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,13 @@
namespace Undabot\JsonApi\Implementation\Factory;

use Assert\Assertion;
use InvalidArgumentException;
use Assert\AssertionFailedException;
use Undabot\JsonApi\Definition\Exception\Request\InvalidParameterValueException;
use Undabot\JsonApi\Definition\Model\Request\Pagination\PaginationInterface;
use Undabot\JsonApi\Implementation\Model\Request\Pagination\CursorBasedPagination;
use Undabot\JsonApi\Implementation\Model\Request\Pagination\OffsetBasedPagination;
use Undabot\JsonApi\Implementation\Model\Request\Pagination\PageBasedPagination;
use Undabot\JsonApi\Implementation\Model\Source\Source;

class PaginationFactory
{
Expand All @@ -29,16 +32,18 @@ public function fromArray(array $paginationParams): PaginationInterface
return $this->makeOffsetBasedPagination($paginationParams);
}

if (true === \array_key_exists(CursorBasedPagination::PARAM_PAGE_SIZE, $paginationParams)
|| true === \array_key_exists(CursorBasedPagination::PARAM_PAGE_AFTER, $paginationParams)
|| true === \array_key_exists(CursorBasedPagination::PARAM_PAGE_BEFORE, $paginationParams)) {
return $this->makeCursorBasedPagination($paginationParams);
}

$message = sprintf('Couldn\'t create pagination from given params: %s', json_encode($paginationParams));

throw new InvalidArgumentException($message);
throw new InvalidParameterValueException(new Source(null), $message);
}

/**
* @param array<string, int> $paginationParams
*
* @throws \Assert\AssertionFailedException
*/
/** @param array<string, int> $paginationParams */
private function makePageBasedPagination(array $paginationParams): PageBasedPagination
{
Assertion::keyExists($paginationParams, PageBasedPagination::PARAM_PAGE_SIZE);
Expand All @@ -53,29 +58,15 @@ private function makePageBasedPagination(array $paginationParams): PageBasedPagi
]
);

foreach ($paginationParams as $paginationParam) {
Assertion::integerish(
$paginationParam,
sprintf('Params must be integer(ish): %s', $paginationParam)
);
Assertion::greaterThan(
$paginationParam,
0,
sprintf('Params can\'t be zero: %s', $paginationParam)
);
}
$this->validatePaginationParams($paginationParams);

return new PageBasedPagination(
(int) $paginationParams[PageBasedPagination::PARAM_PAGE_NUMBER],
(int) $paginationParams[PageBasedPagination::PARAM_PAGE_SIZE]
(int) $paginationParams[PageBasedPagination::PARAM_PAGE_SIZE],
);
}

/**
* @param array<string, int> $paginationParams
*
* @throws \Assert\AssertionFailedException
*/
/** @param array<string, int> $paginationParams */
private function makeOffsetBasedPagination(array $paginationParams): OffsetBasedPagination
{
Assertion::keyExists($paginationParams, OffsetBasedPagination::PARAM_PAGE_OFFSET);
Expand All @@ -90,26 +81,50 @@ private function makeOffsetBasedPagination(array $paginationParams): OffsetBased
]
);

$limit = $paginationParams[OffsetBasedPagination::PARAM_PAGE_LIMIT];
Assertion::integerish(
$limit,
sprintf('Param must be integer(ish): %s', $limit)
);
Assertion::greaterThan(
$limit,
0,
sprintf('Param can\'t be zero: %s', $limit)
);

$offset = $paginationParams[OffsetBasedPagination::PARAM_PAGE_OFFSET];
Assertion::integerish(
$offset,
sprintf('Param must be integer(ish): %s', $offset)
);
$this->validatePaginationParams($paginationParams);

return new OffsetBasedPagination(
(int) $paginationParams[OffsetBasedPagination::PARAM_PAGE_OFFSET],
(int) $paginationParams[OffsetBasedPagination::PARAM_PAGE_LIMIT]
(int) $paginationParams[OffsetBasedPagination::PARAM_PAGE_LIMIT],
);
}

/** @param array<string, int> $paginationParams */
private function makeCursorBasedPagination(array $paginationParams): CursorBasedPagination
{
$size = $paginationParams[CursorBasedPagination::PARAM_PAGE_SIZE] ?? null;
if (null !== $size) {
$this->validatePaginationParams([CursorBasedPagination::PARAM_PAGE_SIZE => $paginationParams[CursorBasedPagination::PARAM_PAGE_SIZE]]);
$size = (int) $size;
}

return new CursorBasedPagination(
$paginationParams[CursorBasedPagination::PARAM_PAGE_AFTER] ?? null,
$paginationParams[CursorBasedPagination::PARAM_PAGE_BEFORE] ?? null,
$size,
);
}


/**
* @param array<string, int> $paginationParams
*
* @throws InvalidParameterValueException
* */
private function validatePaginationParams(array $paginationParams): void
{
foreach ($paginationParams as $paginationParam) {
try {
Assertion::integerish($paginationParam);
Assertion::greaterThan($paginationParam, 0);
} catch (AssertionFailedException $exception) {
throw new InvalidParameterValueException(
new Source(null, "page[$paginationParam]"),
"page[$paginationParam] must be a positive integer",
0,
$exception,
);
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
<?php

declare(strict_types=1);

namespace Undabot\JsonApi\Implementation\Model\Request\Pagination;

use Undabot\JsonApi\Definition\Exception\Request\InvalidParameterValueException;
use Undabot\JsonApi\Definition\Model\Request\Pagination\PaginationInterface;
use Undabot\JsonApi\Implementation\Model\Source\Source;

class CursorBasedPagination implements PaginationInterface
{
public const PARAM_PAGE_AFTER = 'after';
public const PARAM_PAGE_BEFORE = 'before';
public const PARAM_PAGE_SIZE = 'size';

public function __construct(private ?string $after, private ?string $before, private ?int $size)
{
if (null === $this->after && null === $this->before && null === $this->size) {
throw new InvalidParameterValueException(new Source(null), 'At least one of the params must be not null.');
}
}

public function getSize(): int
{
return $this->size ?? 0;
}

public function getAfter(): ?string
{
return $this->after;
}

public function getBefore(): ?string
{
return $this->before;
}

public function getOffset(): int
{
throw new \LogicException('Cursor based pagination does not have offset');
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,16 +11,8 @@ class OffsetBasedPagination implements PaginationInterface
public const PARAM_PAGE_OFFSET = 'offset';
public const PARAM_PAGE_LIMIT = 'limit';

/** @var int */
private $offset;

/** @var int */
private $limit;

public function __construct(int $offset, int $limit)
public function __construct(private int $offset, private int $limit)
{
$this->offset = $offset;
$this->limit = $limit;
}

public function getSize(): int
Expand All @@ -32,4 +24,14 @@ public function getOffset(): int
{
return $this->offset;
}

public function getAfter(): ?string
{
return null;
}

public function getBefore(): ?string
{
return null;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,16 +11,8 @@ class PageBasedPagination implements PaginationInterface
public const PARAM_PAGE_NUMBER = 'number';
public const PARAM_PAGE_SIZE = 'size';

/** @var int */
private $pageNumber;

/** @var int */
private $size;

public function __construct(int $pageNumber, int $size)
public function __construct(private int $pageNumber, private int $size)
{
$this->pageNumber = $pageNumber;
$this->size = $size;
}

public function getPageNumber(): int
Expand All @@ -37,4 +29,14 @@ public function getOffset(): int
{
return ($this->getPageNumber() - 1) * $this->getSize();
}

public function getAfter(): ?string
{
return null;
}

public function getBefore(): ?string
{
return null;
}
}
Loading