Skip to content

Commit febe3e0

Browse files
vjiksamdark
andauthored
Introduce PageToken class (#159)
Co-authored-by: Alexander Makarov <sam@rmcreative.ru>
1 parent 91295cc commit febe3e0

File tree

9 files changed

+232
-170
lines changed

9 files changed

+232
-170
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@
1212
- Bug #155: Fix `Sort` configuration preparation (@vjik)
1313
- Bug #155: Fix same named order fields in `Sort` were not overriding previous ones (@vjik)
1414
- New #158: Add methods `PaginatorInterface::isSortable()` and `PaginatorInterface::withSort()` (@vjik)
15+
- Chg #159: Replace `withNextPageToken()` and `withPreviousPageToken()` of `PaginatorInterface` with `withToken()`,
16+
`getNextPageToken()` with `getNextToken()`, `getPreviousPageToken()` with `getPreviousToken()`, and add `getToken()`.
17+
These methods use new `PageToken` class (@vjik)
1518

1619
## 1.0.1 January 25, 2023
1720

README.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,14 +27,14 @@ Features are:
2727

2828
## Requirements
2929

30-
- PHP 8.0 or higher.
30+
- PHP 8.1 or higher.
3131

3232
## Installation
3333

3434
The package could be installed with composer:
3535

3636
```shell
37-
composer require yiisoft/data --prefer-dist
37+
composer require yiisoft/data
3838
```
3939

4040
## Concepts
@@ -334,7 +334,7 @@ $dataReader = (new MyDataReader(...))
334334

335335
$paginator = (new KeysetPaginator($dataReader))
336336
->withPageSize(10)
337-
->withNextPageToken('13');
337+
->withToken(PageToken::next('13'));
338338
```
339339

340340
When displaying first page ID (or another field name to paginate by) of the item displayed last is used with `withNextPageToken()`

src/Paginator/KeysetPaginator.php

Lines changed: 25 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -61,8 +61,7 @@ final class KeysetPaginator implements PaginatorInterface
6161
* @var int Maximum number of items per page.
6262
*/
6363
private int $pageSize = self::DEFAULT_PAGE_SIZE;
64-
private ?string $firstValue = null;
65-
private ?string $lastValue = null;
64+
private ?PageToken $token = null;
6665
private ?string $currentFirstValue = null;
6766
private ?string $currentLastValue = null;
6867

@@ -139,20 +138,16 @@ public function __clone()
139138
$this->currentLastValue = null;
140139
}
141140

142-
public function withNextPageToken(?string $token): static
141+
public function withToken(?PageToken $token): static
143142
{
144143
$new = clone $this;
145-
$new->firstValue = null;
146-
$new->lastValue = $token;
144+
$new->token = $token;
147145
return $new;
148146
}
149147

150-
public function withPreviousPageToken(?string $token): static
148+
public function getToken(): ?PageToken
151149
{
152-
$new = clone $this;
153-
$new->firstValue = $token;
154-
$new->lastValue = null;
155-
return $new;
150+
return $this->token;
156151
}
157152

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

204-
if ($this->isGoingToPreviousPage()) {
199+
if ($this->token?->isPrevious === true) {
205200
$sort = $this->reverseSort($sort);
206201
$dataReader = $dataReader->withSort($sort);
207202
}
208203

209-
if ($this->isGoingSomewhere()) {
204+
if ($this->token !== null) {
210205
$dataReader = $dataReader->withFilter($this->getFilter($sort));
211206
$this->hasPreviousPage = $this->previousPageExist($dataReader, $sort);
212207
}
213208

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

216-
if ($this->isGoingToPreviousPage()) {
211+
if ($this->token?->isPrevious === true) {
217212
$data = $this->reverseData($data);
218213
}
219214

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

243-
public function getPreviousPageToken(): ?string
238+
public function getPreviousToken(): ?PageToken
244239
{
245-
return $this->isOnFirstPage() ? null : $this->currentFirstValue;
240+
return $this->isOnFirstPage()
241+
? null
242+
: ($this->currentFirstValue === null ? null : PageToken::previous($this->currentFirstValue));
246243
}
247244

248-
public function getNextPageToken(): ?string
245+
public function getNextToken(): ?PageToken
249246
{
250-
return $this->isOnLastPage() ? null : $this->currentLastValue;
247+
return $this->isOnLastPage()
248+
? null
249+
: ($this->currentLastValue === null ? null : PageToken::next($this->currentLastValue));
251250
}
252251

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

270269
public function isOnFirstPage(): bool
271270
{
272-
if ($this->lastValue === null && $this->firstValue === null) {
271+
if ($this->token === null) {
273272
return true;
274273
}
275274

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

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

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

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

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

397-
/**
398-
* @psalm-suppress NullableReturnStatement, InvalidNullableReturnType, PossiblyNullArgument The code calling this
399-
* method must ensure that at least one of the properties `$firstValue` or `$lastValue` is not `null`.
400-
*/
401-
private function getValue(): string
402-
{
403-
return $this->isGoingToPreviousPage() ? $this->firstValue : $this->lastValue;
404-
}
405-
406402
private function reverseSort(Sort $sort): Sort
407403
{
408404
$order = $sort->getOrder();
@@ -426,14 +422,4 @@ private function getFieldAndSortingFromSort(Sort $sort): array
426422
reset($order) === 'asc' ? SORT_ASC : SORT_DESC,
427423
];
428424
}
429-
430-
private function isGoingToPreviousPage(): bool
431-
{
432-
return $this->firstValue !== null && $this->lastValue === null;
433-
}
434-
435-
private function isGoingSomewhere(): bool
436-
{
437-
return $this->firstValue !== null || $this->lastValue !== null;
438-
}
439425
}

src/Paginator/OffsetPaginator.php

Lines changed: 25 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -40,9 +40,9 @@
4040
final class OffsetPaginator implements PaginatorInterface
4141
{
4242
/**
43-
* @var int Current page number.
43+
* @var PageToken Current page token
4444
*/
45-
private int $currentPage = 1;
45+
private PageToken $token;
4646

4747
/**
4848
* @var int Maximum number of items per page.
@@ -85,16 +85,12 @@ public function __construct(ReadableDataInterface $dataReader)
8585
}
8686

8787
$this->dataReader = $dataReader;
88+
$this->token = PageToken::next('1');
8889
}
8990

90-
public function withNextPageToken(?string $token): static
91+
public function withToken(?PageToken $token): static
9192
{
92-
return $this->withCurrentPage((int) $token);
93-
}
94-
95-
public function withPreviousPageToken(?string $token): static
96-
{
97-
return $this->withCurrentPage((int) $token);
93+
return $this->withCurrentPage($token === null ? 1 : (int)$token->value);
9894
}
9995

10096
public function withPageSize(int $pageSize): static
@@ -124,18 +120,23 @@ public function withCurrentPage(int $page): self
124120
}
125121

126122
$new = clone $this;
127-
$new->currentPage = $page;
123+
$new->token = PageToken::next((string) $page);
128124
return $new;
129125
}
130126

131-
public function getNextPageToken(): ?string
127+
public function getToken(): PageToken
132128
{
133-
return $this->isOnLastPage() ? null : (string) ($this->currentPage + 1);
129+
return $this->token;
134130
}
135131

136-
public function getPreviousPageToken(): ?string
132+
public function getNextToken(): ?PageToken
137133
{
138-
return $this->isOnFirstPage() ? null : (string) ($this->currentPage - 1);
134+
return $this->isOnLastPage() ? null : PageToken::next((string) ($this->getCurrentPage() + 1));
135+
}
136+
137+
public function getPreviousToken(): ?PageToken
138+
{
139+
return $this->isOnFirstPage() ? null : PageToken::next((string) ($this->getCurrentPage() - 1));
139140
}
140141

141142
public function getPageSize(): int
@@ -150,7 +151,7 @@ public function getPageSize(): int
150151
*/
151152
public function getCurrentPage(): int
152153
{
153-
return $this->currentPage;
154+
return (int) $this->token->value;
154155
}
155156

156157
public function getCurrentPageSize(): int
@@ -161,11 +162,13 @@ public function getCurrentPageSize(): int
161162
return $this->getTotalItems();
162163
}
163164

164-
if ($this->currentPage < $pages) {
165+
$currentPage = $this->getCurrentPage();
166+
167+
if ($currentPage < $pages) {
165168
return $this->pageSize;
166169
}
167170

168-
if ($this->currentPage === $pages) {
171+
if ($currentPage === $pages) {
169172
return $this->getTotalItems() - $this->getOffset();
170173
}
171174

@@ -179,7 +182,7 @@ public function getCurrentPageSize(): int
179182
*/
180183
public function getOffset(): int
181184
{
182-
return $this->pageSize * ($this->currentPage - 1);
185+
return $this->pageSize * ($this->getCurrentPage() - 1);
183186
}
184187

185188
/**
@@ -228,7 +231,7 @@ public function getSort(): ?Sort
228231
*/
229232
public function read(): iterable
230233
{
231-
if ($this->currentPage > $this->getInternalTotalPages()) {
234+
if ($this->getCurrentPage() > $this->getInternalTotalPages()) {
232235
throw new PaginatorException('Page not found.');
233236
}
234237

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

249252
public function isOnFirstPage(): bool
250253
{
251-
return $this->currentPage === 1;
254+
return $this->token->value === '1';
252255
}
253256

254257
public function isOnLastPage(): bool
255258
{
256-
if ($this->currentPage > $this->getInternalTotalPages()) {
259+
if ($this->getCurrentPage() > $this->getInternalTotalPages()) {
257260
throw new PaginatorException('Page not found.');
258261
}
259262

260-
return $this->currentPage === $this->getInternalTotalPages();
263+
return $this->getCurrentPage() === $this->getInternalTotalPages();
261264
}
262265

263266
public function isPaginationRequired(): bool

src/Paginator/PageToken.php

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Yiisoft\Data\Paginator;
6+
7+
final class PageToken
8+
{
9+
private function __construct(
10+
public readonly string $value,
11+
public readonly bool $isPrevious,
12+
) {
13+
}
14+
15+
public static function previous(string $value): self
16+
{
17+
return new self($value, true);
18+
}
19+
20+
public static function next(string $value): self
21+
{
22+
return new self($value, false);
23+
}
24+
}

src/Paginator/PaginatorInterface.php

Lines changed: 13 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -26,22 +26,15 @@ interface PaginatorInterface extends ReadableDataInterface
2626
public const DEFAULT_PAGE_SIZE = 10;
2727

2828
/**
29-
* Get a new instance with token for the next page set.
29+
* Get a new instance with page token.
3030
*
31-
* @param string|null $token Token for the next page. Null if current page is last.
31+
* @param PageToken|null $token Page token. `Null` if current page is first.
3232
*
3333
* @return static New instance.
34-
*/
35-
public function withNextPageToken(?string $token): static;
36-
37-
/**
38-
* Get a new instance with token for the previous page set.
39-
*
40-
* @param string|null $token Token for the previous page. Null if current page is first.
4134
*
42-
* @return static New instance.
35+
* @see PageToken
4336
*/
44-
public function withPreviousPageToken(?string $token): static;
37+
public function withToken(?PageToken $token): static;
4538

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

50+
/**
51+
* @return PageToken|null Current page token or `null` if not set.
52+
*/
53+
public function getToken(): ?PageToken;
54+
5755
/**
5856
* Get token for the next page.
5957
*
60-
* @return string|null Token for the next page. Null if current page is last.
58+
* @return PageToken|null Page token for the next page. `null` if current page is last.
6159
*/
62-
public function getNextPageToken(): ?string;
60+
public function getNextToken(): ?PageToken;
6361

6462
/**
6563
* Get token for the previous page.
6664
*
67-
* @return string|null Token for the previous page. Null if current page is first.
65+
* @return PageToken|null Page token for the previous page. `null` if current page is first.
6866
*/
69-
public function getPreviousPageToken(): ?string;
67+
public function getPreviousToken(): ?PageToken;
7068

7169
/**
7270
* Get maximum number of items per page.

0 commit comments

Comments
 (0)