diff --git a/CHANGELOG.md b/CHANGELOG.md index 4ede54c..8e13920 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -37,6 +37,7 @@ - Enh #190: Use `str_contains` for case-sensitive match in `LikeHandler` (@samdark) - Enh #194: Improve psalm annotations in `LimitableDataInterface` (@vjik) - Bug #195: Fix invalid count in `IterableDataReader` when limit or/and offset used (@vjik) +- Enh #201: Disable sorting when limit is set explicitly in a paginator (@samdark) - Enh #202: Check that correct sort is passed to `withSort()` of keyset paginator (@samdark) - Enh #207: More secific Psalm type for OffsetPaginator::withCurrentPage() (@samdark) diff --git a/src/Paginator/OffsetPaginator.php b/src/Paginator/OffsetPaginator.php index c97e7a8..8021197 100644 --- a/src/Paginator/OffsetPaginator.php +++ b/src/Paginator/OffsetPaginator.php @@ -224,15 +224,22 @@ public function getTotalPages(): int return (int) ceil($this->getTotalItems() / $this->pageSize); } + /** + * @psalm-assert-if-true SortableDataInterface $this->dataReader + */ public function isSortable(): bool { + if ($this->dataReader instanceof LimitableDataInterface && $this->dataReader->getLimit() !== null) { + return false; + } + return $this->dataReader instanceof SortableDataInterface; } public function withSort(?Sort $sort): static { - if (!$this->dataReader instanceof SortableDataInterface) { - throw new LogicException('Data reader does not support sorting.'); + if (!$this->isSortable()) { + throw new LogicException('Changing sorting is not supported.'); } $new = clone $this; @@ -245,6 +252,9 @@ public function getSort(): ?Sort return $this->dataReader instanceof SortableDataInterface ? $this->dataReader->getSort() : null; } + /** + * @psalm-assert-if-true FilterableDataInterface $this->dataReader + */ public function isFilterable(): bool { return $this->dataReader instanceof FilterableDataInterface; @@ -252,8 +262,8 @@ public function isFilterable(): bool public function withFilter(FilterInterface $filter): static { - if (!$this->dataReader instanceof FilterableDataInterface) { - throw new LogicException('Data reader does not support filtering.'); + if (!$this->isFilterable()) { + throw new LogicException('Changing filtering is not supported.'); } $new = clone $this; diff --git a/src/Paginator/PaginatorInterface.php b/src/Paginator/PaginatorInterface.php index 87be22d..6c9bd06 100644 --- a/src/Paginator/PaginatorInterface.php +++ b/src/Paginator/PaginatorInterface.php @@ -95,7 +95,7 @@ public function getPageSize(): int; public function getCurrentPageSize(): int; /** - * @return bool Whether sorting is supported. + * @return bool Whether changing sorting via {@see withSorting()} is supported. */ public function isSortable(): bool; @@ -104,7 +104,7 @@ public function isSortable(): bool; * * @param Sort|null $sort Sorting criteria or null for no sorting. * - * @throws LogicException When sorting isn't supported. + * @throws LogicException When changing sorting isn't supported. * @return static New instance. */ public function withSort(?Sort $sort): static; @@ -117,7 +117,7 @@ public function withSort(?Sort $sort): static; public function getSort(): ?Sort; /** - * @return bool Whether filtering is supported. + * @return bool Whether changing filter via {@see withFilter()} is supported. */ public function isFilterable(): bool; @@ -126,7 +126,7 @@ public function isFilterable(): bool; * * @param FilterInterface $filter Data reading criteria. * - * @throws LogicException When filtering isn't supported. + * @throws LogicException When changing filter isn't supported. * @return static New instance. */ public function withFilter(FilterInterface $filter): static; diff --git a/tests/Paginator/OffsetPaginatorTest.php b/tests/Paginator/OffsetPaginatorTest.php index d91f791..03419c5 100644 --- a/tests/Paginator/OffsetPaginatorTest.php +++ b/tests/Paginator/OffsetPaginatorTest.php @@ -530,8 +530,9 @@ public function testImmutability(): void public static function dataIsSupportSorting(): array { return [ - [true, new IterableDataReader([])], - [false, new StubOffsetData()], + 'IterableDataReader' => [true, new IterableDataReader([])], + 'StubOffsetData' => [false, new StubOffsetData()], + 'StubOffsetDataWithLimit' => [false, (new StubOffsetData())->withLimit(10)], ]; } @@ -562,7 +563,7 @@ public function testWithSortNonSortableData(): void $paginator = new OffsetPaginator(new StubOffsetData()); $this->expectException(LogicException::class); - $this->expectExceptionMessage('Data reader does not support sorting.'); + $this->expectExceptionMessage('Changing sorting is not supported.'); $paginator->withSort(null); } @@ -601,7 +602,7 @@ public function testWithFilterNonFilterableData(): void $paginator = new OffsetPaginator(new StubOffsetData()); $this->expectException(LogicException::class); - $this->expectExceptionMessage('Data reader does not support filtering.'); + $this->expectExceptionMessage('Changing filtering is not supported.'); $paginator->withFilter(new Equals('id', 2)); } diff --git a/tests/Support/StubOffsetData.php b/tests/Support/StubOffsetData.php index e756dac..0dcbc36 100644 --- a/tests/Support/StubOffsetData.php +++ b/tests/Support/StubOffsetData.php @@ -15,6 +15,12 @@ final class StubOffsetData implements CountableDataInterface, LimitableDataInterface { + /** + * @var int|null + * @psalm-var non-negative-int + */ + private ?int $limit = null; + public function read(): iterable { return []; @@ -32,7 +38,9 @@ public function count(): int public function withLimit(?int $limit): static { - return $this; + $new = clone $this; + $new->limit = $limit; + return $new; } public function withOffset(int $offset): static @@ -40,9 +48,9 @@ public function withOffset(int $offset): static return $this; } - public function getLimit(): int + public function getLimit(): ?int { - return 0; + return $this->limit; } public function getOffset(): int