From c9e32d41a2cf2dc11676aff38738ac15d2121566 Mon Sep 17 00:00:00 2001 From: Sergei Predvoditelev Date: Wed, 11 Jun 2025 17:45:54 +0300 Subject: [PATCH 1/3] Use default order from `Sort` in `KeysetPaginator` --- CHANGELOG.md | 2 ++ composer.json | 2 +- src/Paginator/KeysetPaginator.php | 26 ++++++++++++++++--------- src/Reader/Sort.php | 15 +++++++++++++- tests/Paginator/KeysetPaginatorTest.php | 15 ++++++++------ 5 files changed, 43 insertions(+), 17 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 090d3e2..7a114f4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -51,6 +51,8 @@ - Chg #219: Don't check correctness of current page in `PaginatorInterface::isOnLastPage()` method (@vjik) - Chg #219: Rename `PaginatorException` to `InvalidPageException` (@vjik) - Chg #211, #221: Change PHP constraint in `composer.json` to `8.1 - 8.4` (@vjik) +- New #223: Add `Sort::getDefaultOrder()` method (@vjik) +- Enh #223: `KeysetPaginator` now uses default order from `Sort` when no sort is set (@vjik) ## 1.0.1 January 25, 2023 diff --git a/composer.json b/composer.json index 9fc0198..67d035c 100644 --- a/composer.json +++ b/composer.json @@ -38,7 +38,7 @@ "require-dev": { "maglnet/composer-require-checker": "^4.7.1", "phpunit/phpunit": "^10.5.46", - "rector/rector": "^2.0.15", + "rector/rector": "^2.0.18", "roave/infection-static-analysis-plugin": "^1.35", "spatie/phpunit-watcher": "^1.24", "vimeo/psalm": "^5.26.1 || ^6.10.3" diff --git a/src/Paginator/KeysetPaginator.php b/src/Paginator/KeysetPaginator.php index 2d4e90f..d0fec0f 100644 --- a/src/Paginator/KeysetPaginator.php +++ b/src/Paginator/KeysetPaginator.php @@ -19,6 +19,7 @@ use Yiisoft\Data\Reader\SortableDataInterface; use function array_reverse; +use function array_slice; use function count; use function key; use function reset; @@ -121,10 +122,7 @@ public function __construct(ReadableDataInterface $dataReader) throw new InvalidArgumentException('Limited data readers are not supported by keyset pagination.'); } - $sort = $dataReader->getSort(); - $this->assertSort($sort); - - $this->dataReader = $dataReader; + $this->dataReader = $this->prepareSortInDataReader($dataReader, $dataReader->getSort()); } public function __clone() @@ -255,10 +253,8 @@ public function isSortable(): bool public function withSort(?Sort $sort): static { - $this->assertSort($sort); - $new = clone $this; - $new->dataReader = $this->dataReader->withSort($sort); + $new->dataReader = $this->prepareSortInDataReader($this->dataReader, $sort); return $new; } @@ -436,14 +432,26 @@ private function getFieldAndSortingFromSort(Sort $sort): array ]; } - private function assertSort(?Sort $sort): void + /** + * @param ReadableDataInterface&LimitableDataInterface&FilterableDataInterface&SortableDataInterface $dataReader + * @return ReadableDataInterface&LimitableDataInterface&FilterableDataInterface&SortableDataInterface + */ + private function prepareSortInDataReader(ReadableDataInterface $dataReader, ?Sort $sort): ReadableDataInterface { if ($sort === null) { throw new InvalidArgumentException('Data sorting should be configured to work with keyset pagination.'); } if (empty($sort->getOrder())) { - throw new InvalidArgumentException('Data should be always sorted to work with keyset pagination.'); + $defaultOrder = $sort->getDefaultOrder(); + if (empty($defaultOrder)) { + throw new InvalidArgumentException('Data should be always sorted to work with keyset pagination.'); + } + $sort = $sort->withOrder( + array_slice($defaultOrder, 0, 1, true) + ); } + + return $dataReader->withSort($sort); } } diff --git a/src/Reader/Sort.php b/src/Reader/Sort.php index 6ed3a26..dddf571 100644 --- a/src/Reader/Sort.php +++ b/src/Reader/Sort.php @@ -59,7 +59,7 @@ final class Sort private bool $withDefaultSorting = true; /** - * @var array Logical fields to order by in form of [name => direction]. + * @var array Logical fields to order by in form of `[name => direction]`. * @psalm-var TOrder */ private array $currentOrder = []; @@ -321,4 +321,17 @@ public function hasFieldInConfig(string $name): bool { return isset($this->config[$name]); } + + /** + * Get a default order for logical fields. + * + * @return TOrder + */ + public function getDefaultOrder(): array + { + return array_map( + static fn(array $item) => $item['default'], + $this->config + ); + } } diff --git a/tests/Paginator/KeysetPaginatorTest.php b/tests/Paginator/KeysetPaginatorTest.php index 8a26ee3..a4c49c4 100644 --- a/tests/Paginator/KeysetPaginatorTest.php +++ b/tests/Paginator/KeysetPaginatorTest.php @@ -166,15 +166,18 @@ public function testThrowsExceptionForWithSortNull(): void (new KeysetPaginator($dataReader))->withSort(null); } - public function testThrowsExceptionWhenNotSorted(): void + public function testDefaultOrderUsage(): void { - $sort = Sort::only(['id', 'name']); + $sort = Sort::only(['name', 'id']); $dataReader = (new IterableDataReader(self::getDataSet()))->withSort($sort); + $paginator = new KeysetPaginator($dataReader); - $this->expectException(InvalidArgumentException::class); - $this->expectExceptionMessage('Data should be always sorted to work with keyset pagination.'); + $result = $paginator->read(); - new KeysetPaginator($dataReader); + $this->assertSame( + self::getDataSet([4, 3, 2, 0, 1]), + array_values($this->iterableToArray($result)), + ); } public function testThrowsExceptionForWithSortNotSorted(): void @@ -185,7 +188,7 @@ public function testThrowsExceptionForWithSortNotSorted(): void $this->expectException(InvalidArgumentException::class); $this->expectExceptionMessage('Data should be always sorted to work with keyset pagination.'); - (new KeysetPaginator($dataReader))->withSort(Sort::only(['id', 'name'])); + (new KeysetPaginator($dataReader))->withSort(Sort::only([])); } public function testPageSizeCannotBeLessThanOne(): void From 6ad172fd24cb2d7fdc8c7e31fa2f7491e2db9b4e Mon Sep 17 00:00:00 2001 From: StyleCI Bot Date: Wed, 11 Jun 2025 14:46:22 +0000 Subject: [PATCH 2/3] Apply fixes from StyleCI --- src/Paginator/KeysetPaginator.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Paginator/KeysetPaginator.php b/src/Paginator/KeysetPaginator.php index d0fec0f..707afa3 100644 --- a/src/Paginator/KeysetPaginator.php +++ b/src/Paginator/KeysetPaginator.php @@ -433,8 +433,8 @@ private function getFieldAndSortingFromSort(Sort $sort): array } /** - * @param ReadableDataInterface&LimitableDataInterface&FilterableDataInterface&SortableDataInterface $dataReader - * @return ReadableDataInterface&LimitableDataInterface&FilterableDataInterface&SortableDataInterface + * @param FilterableDataInterface&LimitableDataInterface&ReadableDataInterface&SortableDataInterface $dataReader + * @return FilterableDataInterface&LimitableDataInterface&ReadableDataInterface&SortableDataInterface */ private function prepareSortInDataReader(ReadableDataInterface $dataReader, ?Sort $sort): ReadableDataInterface { From 350b5ecd3908028f850a3865f8bcadcaf0c9823a Mon Sep 17 00:00:00 2001 From: Sergei Predvoditelev Date: Wed, 11 Jun 2025 18:04:03 +0300 Subject: [PATCH 3/3] test --- tests/Paginator/KeysetPaginatorTest.php | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/tests/Paginator/KeysetPaginatorTest.php b/tests/Paginator/KeysetPaginatorTest.php index a4c49c4..07b18dd 100644 --- a/tests/Paginator/KeysetPaginatorTest.php +++ b/tests/Paginator/KeysetPaginatorTest.php @@ -180,6 +180,29 @@ public function testDefaultOrderUsage(): void ); } + public function testDefaultOrderUsageInPrevious(): void + { + $sort = Sort::only(['name', 'id']); + $data = [ + ['id' => 2, 'name' => 'A'], + ['id' => 1, 'name' => 'A'], + ['id' => 3, 'name' => 'B'], + ]; + $dataReader = (new IterableDataReader($data))->withSort($sort); + $paginator = (new KeysetPaginator($dataReader)) + ->withToken(PageToken::previous('B')); + + $result = $paginator->read(); + + $this->assertSame( + [ + ['id' => 2, 'name' => 'A'], + ['id' => 1, 'name' => 'A'], + ], + array_values($this->iterableToArray($result)), + ); + } + public function testThrowsExceptionForWithSortNotSorted(): void { $sort = Sort::only(['id', 'name'])->withOrderString('id');