diff --git a/CHANGELOG.md b/CHANGELOG.md index 3a6a0fde..b7f1dd8b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ - 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) +- New #164: Add methods `PaginatorInterface::isFilterable()` and `PaginatorInterface::withFilter()` (@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) diff --git a/src/Paginator/KeysetPaginator.php b/src/Paginator/KeysetPaginator.php index 512d6c2c..863841d6 100644 --- a/src/Paginator/KeysetPaginator.php +++ b/src/Paginator/KeysetPaginator.php @@ -266,6 +266,18 @@ public function getSort(): ?Sort return $this->dataReader->getSort(); } + public function isFilterable(): bool + { + return true; + } + + public function withFilter(FilterInterface $filter): static + { + $new = clone $this; + $new->dataReader = $this->dataReader->withFilter($filter); + return $new; + } + public function isOnFirstPage(): bool { if ($this->token === null) { diff --git a/src/Paginator/OffsetPaginator.php b/src/Paginator/OffsetPaginator.php index 85fe4933..d6ec25ef 100644 --- a/src/Paginator/OffsetPaginator.php +++ b/src/Paginator/OffsetPaginator.php @@ -8,6 +8,8 @@ use InvalidArgumentException; use LogicException; use Yiisoft\Data\Reader\CountableDataInterface; +use Yiisoft\Data\Reader\FilterableDataInterface; +use Yiisoft\Data\Reader\FilterInterface; use Yiisoft\Data\Reader\LimitableDataInterface; use Yiisoft\Data\Reader\OffsetableDataInterface; use Yiisoft\Data\Reader\ReadableDataInterface; @@ -230,6 +232,22 @@ public function getSort(): ?Sort return $this->dataReader instanceof SortableDataInterface ? $this->dataReader->getSort() : null; } + public function isFilterable(): bool + { + return $this->dataReader instanceof FilterableDataInterface; + } + + public function withFilter(FilterInterface $filter): static + { + if (!$this->dataReader instanceof FilterableDataInterface) { + throw new LogicException('Data reader does not support filtering.'); + } + + $new = clone $this; + $new->dataReader = $this->dataReader->withFilter($filter); + return $new; + } + /** * @psalm-return Generator */ diff --git a/src/Paginator/PaginatorInterface.php b/src/Paginator/PaginatorInterface.php index eee45647..5d291c28 100644 --- a/src/Paginator/PaginatorInterface.php +++ b/src/Paginator/PaginatorInterface.php @@ -4,6 +4,8 @@ namespace Yiisoft\Data\Paginator; +use LogicException; +use Yiisoft\Data\Reader\FilterInterface; use Yiisoft\Data\Reader\ReadableDataInterface; use Yiisoft\Data\Reader\Sort; @@ -100,9 +102,8 @@ public function isSortable(): bool; * * @param Sort|null $sort Sorting criteria or null for no sorting. * + * @throws LogicException When sorting is not supported. * @return static New instance. - * - * @throw LogicException When sorting is not supported. */ public function withSort(?Sort $sort): static; @@ -113,6 +114,21 @@ public function withSort(?Sort $sort): static; */ public function getSort(): ?Sort; + /** + * @return bool Whether filtering is supported. + */ + public function isFilterable(): bool; + + /** + * Returns new instance with data reading criteria set. + * + * @param FilterInterface $filter Data reading criteria. + * + * @throws LogicException When filtering is not supported. + * @return static New instance. + */ + public function withFilter(FilterInterface $filter): static; + /** * Get iterator that could be used to read currently active page items. * diff --git a/tests/Paginator/KeysetPaginatorTest.php b/tests/Paginator/KeysetPaginatorTest.php index 8b474ead..efec05df 100644 --- a/tests/Paginator/KeysetPaginatorTest.php +++ b/tests/Paginator/KeysetPaginatorTest.php @@ -13,6 +13,7 @@ use Yiisoft\Data\Paginator\KeysetFilterContext; use Yiisoft\Data\Paginator\KeysetPaginator; use Yiisoft\Data\Paginator\PageToken; +use Yiisoft\Data\Reader\Filter\Equals; use Yiisoft\Data\Reader\Filter\GreaterThan; use Yiisoft\Data\Reader\Filter\GreaterThanOrEqual; use Yiisoft\Data\Reader\Filter\LessThan; @@ -1053,6 +1054,30 @@ public function testWithSort(): void $this->assertSame($sort2, $paginator2->getSort()); } + public function testIsFilterable(): void + { + $sort = Sort::only(['id'])->withOrderString('id'); + $reader = (new IterableDataReader([]))->withSort($sort); + $paginator = new KeysetPaginator($reader); + + $this->assertTrue($paginator->isFilterable()); + } + + public function testWithFilter(): void + { + $sort = Sort::only(['id'])->withOrderString('id'); + $reader = (new IterableDataReader([ + 'a' => ['id' => 1], + 'b' => ['id' => 2], + ]))->withSort($sort); + $paginator = new KeysetPaginator($reader); + + $paginatorWithFilter = $paginator->withFilter(new Equals('id', 2)); + + $this->assertNotSame($paginator, $paginatorWithFilter); + $this->assertSame(['b' => ['id' => 2]], $paginatorWithFilter->read()); + } + public function testGetPageToken(): void { $sort = Sort::only(['id'])->withOrderString('id'); diff --git a/tests/Paginator/OffsetPaginatorTest.php b/tests/Paginator/OffsetPaginatorTest.php index 6411d433..5c761880 100644 --- a/tests/Paginator/OffsetPaginatorTest.php +++ b/tests/Paginator/OffsetPaginatorTest.php @@ -13,6 +13,7 @@ use Yiisoft\Data\Paginator\PaginatorException; use Yiisoft\Data\Paginator\PaginatorInterface; use Yiisoft\Data\Reader\CountableDataInterface; +use Yiisoft\Data\Reader\Filter\Equals; use Yiisoft\Data\Reader\Iterable\IterableDataReader; use Yiisoft\Data\Reader\LimitableDataInterface; use Yiisoft\Data\Reader\OffsetableDataInterface; @@ -555,6 +556,45 @@ public function testWithSortNonSortableData(): void $paginator->withSort(null); } + public static function dataIsFilterable(): array + { + return [ + [true, new IterableDataReader([])], + [false, new StubOffsetData()], + ]; + } + + #[DataProvider('dataIsFilterable')] + public function testIsFilterable(bool $expected, ReadableDataInterface $reader): void + { + $paginator = new OffsetPaginator($reader); + + $this->assertSame($expected, $paginator->isFilterable()); + } + + public function testWithFilter(): void + { + $reader = (new IterableDataReader([ + 'a' => ['id' => 1], + 'b' => ['id' => 2], + ])); + $paginator = new OffsetPaginator($reader); + + $paginatorWithFilter = $paginator->withFilter(new Equals('id', 2)); + + $this->assertNotSame($paginator, $paginatorWithFilter); + $this->assertSame(['b' => ['id' => 2]], iterator_to_array($paginatorWithFilter->read())); + } + + public function testWithFilterNonFilterableData(): void + { + $paginator = new OffsetPaginator(new StubOffsetData()); + + $this->expectException(LogicException::class); + $this->expectExceptionMessage('Data reader does not support filtering.'); + $paginator->withFilter(new Equals('id', 2)); + } + public function testWithNulledPageToken(): void { $paginator = (new OffsetPaginator(new StubOffsetData()))->withToken(null);