diff --git a/CHANGELOG.md b/CHANGELOG.md index a09324e..611df27 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,10 @@ # Changelog +# 0.20.0 + +* refactor: `PageableInterface::getPages()` now returns `Iterator` instead of + `Traversable` (non-backward compatible for implementors) + # 0.19.1 * chore: static analysis diff --git a/packages/rekapager-contracts/src/Internal/PageableIterator.php b/packages/rekapager-contracts/src/Internal/PageableIterator.php new file mode 100644 index 0000000..395228d --- /dev/null +++ b/packages/rekapager-contracts/src/Internal/PageableIterator.php @@ -0,0 +1,76 @@ + + * + * For the full copyright and license information, please view the LICENSE file + * that was distributed with this source code. + */ + +namespace Rekalogika\Contracts\Rekapager\Internal; + +use Rekalogika\Contracts\Rekapager\Exception\LogicException; +use Rekalogika\Contracts\Rekapager\PageInterface; + +/** + * @template TKey of array-key + * @template-covariant T + * @implements \Iterator> + */ +final class PageableIterator implements \Iterator +{ + private int $position = 0; + + /** + * @var PageInterface + */ + private ?PageInterface $currentPage; + + /** + * @param PageInterface $startPage + */ + public function __construct( + private readonly PageInterface $startPage, + ) { + $this->currentPage = $this->startPage; + } + + public function current(): mixed + { + if ($this->currentPage === null) { + throw new LogicException('The iterator is not valid'); + } + + return $this->currentPage; + } + + public function next(): void + { + if ($this->currentPage === null) { + return; + } + + $this->currentPage = $this->currentPage->getNextPage(); + $this->position++; + } + + public function key(): mixed + { + return $this->position; + } + + public function valid(): bool + { + return $this->currentPage !== null; + } + + public function rewind(): void + { + $this->currentPage = $this->startPage; + $this->position = 0; + } +} diff --git a/packages/rekapager-contracts/src/PageableInterface.php b/packages/rekapager-contracts/src/PageableInterface.php index 0cdb366..f4770b0 100644 --- a/packages/rekapager-contracts/src/PageableInterface.php +++ b/packages/rekapager-contracts/src/PageableInterface.php @@ -58,9 +58,9 @@ public function getLastPage(): ?PageInterface; * * @param object|null $start The identifier of the starting page. If null, * it will start from the first page. - * @return \Traversable> + * @return \Iterator> */ - public function getPages(?object $start = null): \Traversable; + public function getPages(?object $start = null): \Iterator; /** * Gets the number of items per page. The actual items in the page may be diff --git a/packages/rekapager-contracts/src/Trait/PageableTrait.php b/packages/rekapager-contracts/src/Trait/PageableTrait.php index 34d8405..7866ccf 100644 --- a/packages/rekapager-contracts/src/Trait/PageableTrait.php +++ b/packages/rekapager-contracts/src/Trait/PageableTrait.php @@ -13,6 +13,7 @@ namespace Rekalogika\Contracts\Rekapager\Trait; +use Rekalogika\Contracts\Rekapager\Internal\PageableIterator; use Rekalogika\Contracts\Rekapager\PageInterface; /** @@ -56,9 +57,9 @@ public function getTotalPages(): ?int } /** - * @return \Traversable> + * @return \Iterator> */ - public function getPages(?object $start = null): \Traversable + public function getPages(?object $start = null): \Iterator { if ($start === null) { $page = $this->getFirstPage(); @@ -66,10 +67,6 @@ public function getPages(?object $start = null): \Traversable $page = $this->getPageByIdentifier($start); } - while ($page !== null) { - yield $page; - - $page = $page->getNextPage(); - } + return new PageableIterator($page); } } diff --git a/packages/rekapager-pagerfanta-adapter/src/PagerfantaPageable.php b/packages/rekapager-pagerfanta-adapter/src/PagerfantaPageable.php index 4ed5d6f..d96c444 100644 --- a/packages/rekapager-pagerfanta-adapter/src/PagerfantaPageable.php +++ b/packages/rekapager-pagerfanta-adapter/src/PagerfantaPageable.php @@ -64,7 +64,7 @@ public function getPageIdentifierClass(): string } #[\Override] - public function getPages(?object $start = null): \Traversable + public function getPages(?object $start = null): \Iterator { return $this->pageable->getPages($start); } diff --git a/tests/src/ArchitectureTests/ArchitectureTest.php b/tests/src/ArchitectureTests/ArchitectureTest.php index 5bb6424..09df933 100644 --- a/tests/src/ArchitectureTests/ArchitectureTest.php +++ b/tests/src/ArchitectureTests/ArchitectureTest.php @@ -94,6 +94,7 @@ public function testPackageContracts(): Rule ->classes( Selector::inNamespace('Rekalogika\Contracts\Rekapager'), Selector::classname(\Traversable::class), + Selector::classname(\Iterator::class), Selector::classname(\Countable::class), Selector::classname(\UnexpectedValueException::class), Selector::classname(\RuntimeException::class), @@ -201,6 +202,7 @@ public function testPackageKeysetPagination(): Rule Selector::classname(Base64Url::class), Selector::classname(\Closure::class), Selector::classname(\Traversable::class), + Selector::classname(\Iterator::class), Selector::classname(\ArrayIterator::class), Selector::classname(\BackedEnum::class), Selector::classname(UuidInterface::class), @@ -226,6 +228,7 @@ public function testPackageOffsetPagination(): Rule Selector::classname(\Closure::class), Selector::classname(\IteratorAggregate::class), Selector::classname(\Traversable::class), + Selector::classname(\Iterator::class), ); } @@ -244,6 +247,7 @@ public function testPackagePagerfantaAdapter(): Rule Selector::inNamespace('Rekalogika\Rekapager\Adapter\Common'), Selector::classname(\Closure::class), Selector::classname(\Traversable::class), + Selector::classname(\Iterator::class), ); }