Skip to content
Merged
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
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@
- Bug #155: Fix `Sort` configuration preparation (@vjik)
- Bug #155: Fix same named order fields in `Sort` were not overriding previous ones (@vjik)
- New #158: Add methods `PaginatorInterface::isSortable()` and `PaginatorInterface::withSort()` (@vjik)
- Chg #159: Replace `withNextPageToken()` and `withPreviousPageToken()` of `PaginatorInterface` with `withToken()`,
`getNextPageToken()` with `getNextToken()`, `getPreviousPageToken()` with `getPreviousToken()`, and add `getToken()`.
These methods use new `PageToken` class (@vjik)

## 1.0.1 January 25, 2023

Expand Down
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,14 +27,14 @@ Features are:

## Requirements

- PHP 8.0 or higher.
- PHP 8.1 or higher.

## Installation

The package could be installed with composer:

```shell
composer require yiisoft/data --prefer-dist
composer require yiisoft/data
```

## Concepts
Expand Down Expand Up @@ -334,7 +334,7 @@ $dataReader = (new MyDataReader(...))

$paginator = (new KeysetPaginator($dataReader))
->withPageSize(10)
->withNextPageToken('13');
->withToken(PageToken::next('13'));
```

When displaying first page ID (or another field name to paginate by) of the item displayed last is used with `withNextPageToken()`
Expand Down
64 changes: 25 additions & 39 deletions src/Paginator/KeysetPaginator.php
Original file line number Diff line number Diff line change
Expand Up @@ -61,8 +61,7 @@ final class KeysetPaginator implements PaginatorInterface
* @var int Maximum number of items per page.
*/
private int $pageSize = self::DEFAULT_PAGE_SIZE;
private ?string $firstValue = null;
private ?string $lastValue = null;
private ?PageToken $token = null;
private ?string $currentFirstValue = null;
private ?string $currentLastValue = null;

Expand Down Expand Up @@ -139,20 +138,16 @@ public function __clone()
$this->currentLastValue = null;
}

public function withNextPageToken(?string $token): static
public function withToken(?PageToken $token): static
{
$new = clone $this;
$new->firstValue = null;
$new->lastValue = $token;
$new->token = $token;
return $new;
}

public function withPreviousPageToken(?string $token): static
public function getToken(): ?PageToken
{
$new = clone $this;
$new->firstValue = $token;
$new->lastValue = null;
return $new;
return $this->token;
}

public function withPageSize(int $pageSize): static
Expand Down Expand Up @@ -201,19 +196,19 @@ public function read(): iterable
/** @infection-ignore-all Any value more one in line below will be ignored into `readData()` method */
$dataReader = $this->dataReader->withLimit($this->pageSize + 1);

if ($this->isGoingToPreviousPage()) {
if ($this->token?->isPrevious === true) {
$sort = $this->reverseSort($sort);
$dataReader = $dataReader->withSort($sort);
}

if ($this->isGoingSomewhere()) {
if ($this->token !== null) {
$dataReader = $dataReader->withFilter($this->getFilter($sort));
$this->hasPreviousPage = $this->previousPageExist($dataReader, $sort);
}

$data = $this->readData($dataReader, $sort);

if ($this->isGoingToPreviousPage()) {
if ($this->token?->isPrevious === true) {
$data = $this->reverseData($data);
}

Expand All @@ -240,14 +235,18 @@ public function getCurrentPageSize(): int
return count($this->readCache);
}

public function getPreviousPageToken(): ?string
public function getPreviousToken(): ?PageToken
{
return $this->isOnFirstPage() ? null : $this->currentFirstValue;
return $this->isOnFirstPage()
? null
: ($this->currentFirstValue === null ? null : PageToken::previous($this->currentFirstValue));
}

public function getNextPageToken(): ?string
public function getNextToken(): ?PageToken
{
return $this->isOnLastPage() ? null : $this->currentLastValue;
return $this->isOnLastPage()
? null
: ($this->currentLastValue === null ? null : PageToken::next($this->currentLastValue));
}

public function isSortable(): bool
Expand All @@ -269,7 +268,7 @@ public function getSort(): ?Sort

public function isOnFirstPage(): bool
{
if ($this->lastValue === null && $this->firstValue === null) {
if ($this->token === null) {
return true;
}

Expand Down Expand Up @@ -354,7 +353,10 @@ private function previousPageExist(ReadableDataInterface $dataReader, Sort $sort

private function getFilter(Sort $sort): FilterInterface
{
$value = $this->getValue();
/**
* @psalm-var PageToken $this->token The code calling this method must ensure that page token is not null.
*/
$value = $this->token->value;
[$field, $sorting] = $this->getFieldAndSortingFromSort($sort);

$filter = $sorting === SORT_ASC ? new GreaterThan($field, $value) : new LessThan($field, $value);
Expand All @@ -375,7 +377,10 @@ private function getFilter(Sort $sort): FilterInterface

private function getReverseFilter(Sort $sort): FilterInterface
{
$value = $this->getValue();
/**
* @psalm-var PageToken $this->token The code calling this method must ensure that page token is not null.
*/
$value = $this->token->value;
[$field, $sorting] = $this->getFieldAndSortingFromSort($sort);

$filter = $sorting === SORT_ASC ? new LessThanOrEqual($field, $value) : new GreaterThanOrEqual($field, $value);
Expand All @@ -394,15 +399,6 @@ private function getReverseFilter(Sort $sort): FilterInterface
);
}

/**
* @psalm-suppress NullableReturnStatement, InvalidNullableReturnType, PossiblyNullArgument The code calling this
* method must ensure that at least one of the properties `$firstValue` or `$lastValue` is not `null`.
*/
private function getValue(): string
{
return $this->isGoingToPreviousPage() ? $this->firstValue : $this->lastValue;
}

private function reverseSort(Sort $sort): Sort
{
$order = $sort->getOrder();
Expand All @@ -426,14 +422,4 @@ private function getFieldAndSortingFromSort(Sort $sort): array
reset($order) === 'asc' ? SORT_ASC : SORT_DESC,
];
}

private function isGoingToPreviousPage(): bool
{
return $this->firstValue !== null && $this->lastValue === null;
}

private function isGoingSomewhere(): bool
{
return $this->firstValue !== null || $this->lastValue !== null;
}
}
47 changes: 25 additions & 22 deletions src/Paginator/OffsetPaginator.php
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,9 @@
final class OffsetPaginator implements PaginatorInterface
{
/**
* @var int Current page number.
* @var PageToken Current page token
*/
private int $currentPage = 1;
private PageToken $token;

/**
* @var int Maximum number of items per page.
Expand Down Expand Up @@ -85,16 +85,12 @@ public function __construct(ReadableDataInterface $dataReader)
}

$this->dataReader = $dataReader;
$this->token = PageToken::next('1');
}

public function withNextPageToken(?string $token): static
public function withToken(?PageToken $token): static
{
return $this->withCurrentPage((int) $token);
}

public function withPreviousPageToken(?string $token): static
{
return $this->withCurrentPage((int) $token);
return $this->withCurrentPage($token === null ? 1 : (int)$token->value);
}

public function withPageSize(int $pageSize): static
Expand Down Expand Up @@ -124,18 +120,23 @@ public function withCurrentPage(int $page): self
}

$new = clone $this;
$new->currentPage = $page;
$new->token = PageToken::next((string) $page);
return $new;
}

public function getNextPageToken(): ?string
public function getToken(): PageToken
{
return $this->isOnLastPage() ? null : (string) ($this->currentPage + 1);
return $this->token;
}

public function getPreviousPageToken(): ?string
public function getNextToken(): ?PageToken
{
return $this->isOnFirstPage() ? null : (string) ($this->currentPage - 1);
return $this->isOnLastPage() ? null : PageToken::next((string) ($this->getCurrentPage() + 1));
}

public function getPreviousToken(): ?PageToken
{
return $this->isOnFirstPage() ? null : PageToken::next((string) ($this->getCurrentPage() - 1));
}

public function getPageSize(): int
Expand All @@ -150,7 +151,7 @@ public function getPageSize(): int
*/
public function getCurrentPage(): int
{
return $this->currentPage;
return (int) $this->token->value;
}

public function getCurrentPageSize(): int
Expand All @@ -161,11 +162,13 @@ public function getCurrentPageSize(): int
return $this->getTotalItems();
}

if ($this->currentPage < $pages) {
$currentPage = $this->getCurrentPage();

if ($currentPage < $pages) {
return $this->pageSize;
}

if ($this->currentPage === $pages) {
if ($currentPage === $pages) {
return $this->getTotalItems() - $this->getOffset();
}

Expand All @@ -179,7 +182,7 @@ public function getCurrentPageSize(): int
*/
public function getOffset(): int
{
return $this->pageSize * ($this->currentPage - 1);
return $this->pageSize * ($this->getCurrentPage() - 1);
}

/**
Expand Down Expand Up @@ -228,7 +231,7 @@ public function getSort(): ?Sort
*/
public function read(): iterable
{
if ($this->currentPage > $this->getInternalTotalPages()) {
if ($this->getCurrentPage() > $this->getInternalTotalPages()) {
throw new PaginatorException('Page not found.');
}

Expand All @@ -248,16 +251,16 @@ public function readOne(): array|object|null

public function isOnFirstPage(): bool
{
return $this->currentPage === 1;
return $this->token->value === '1';
}

public function isOnLastPage(): bool
{
if ($this->currentPage > $this->getInternalTotalPages()) {
if ($this->getCurrentPage() > $this->getInternalTotalPages()) {
throw new PaginatorException('Page not found.');
}

return $this->currentPage === $this->getInternalTotalPages();
return $this->getCurrentPage() === $this->getInternalTotalPages();
}

public function isPaginationRequired(): bool
Expand Down
24 changes: 24 additions & 0 deletions src/Paginator/PageToken.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<?php

declare(strict_types=1);

namespace Yiisoft\Data\Paginator;

final class PageToken
{
private function __construct(
public readonly string $value,
public readonly bool $isPrevious,
) {
}

public static function previous(string $value): self
{
return new self($value, true);
}

public static function next(string $value): self
{
return new self($value, false);
}
}
28 changes: 13 additions & 15 deletions src/Paginator/PaginatorInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -26,22 +26,15 @@ interface PaginatorInterface extends ReadableDataInterface
public const DEFAULT_PAGE_SIZE = 10;

/**
* Get a new instance with token for the next page set.
* Get a new instance with page token.
*
* @param string|null $token Token for the next page. Null if current page is last.
* @param PageToken|null $token Page token. `Null` if current page is first.
*
* @return static New instance.
*/
public function withNextPageToken(?string $token): static;

/**
* Get a new instance with token for the previous page set.
*
* @param string|null $token Token for the previous page. Null if current page is first.
*
* @return static New instance.
* @see PageToken
*/
public function withPreviousPageToken(?string $token): static;
public function withToken(?PageToken $token): static;

/**
* Get a new instance with page size set.
Expand All @@ -54,19 +47,24 @@ public function withPreviousPageToken(?string $token): static;
*/
public function withPageSize(int $pageSize): static;

/**
* @return PageToken|null Current page token or `null` if not set.
*/
public function getToken(): ?PageToken;

/**
* Get token for the next page.
*
* @return string|null Token for the next page. Null if current page is last.
* @return PageToken|null Page token for the next page. `null` if current page is last.
*/
public function getNextPageToken(): ?string;
public function getNextToken(): ?PageToken;

/**
* Get token for the previous page.
*
* @return string|null Token for the previous page. Null if current page is first.
* @return PageToken|null Page token for the previous page. `null` if current page is first.
*/
public function getPreviousPageToken(): ?string;
public function getPreviousToken(): ?PageToken;

/**
* Get maximum number of items per page.
Expand Down
Loading