diff --git a/CHANGELOG.md b/CHANGELOG.md index 129c0dadcf7..f2ee9d43f2d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ * Doctrine: Better exception to find which resource is linked to an exception (#3965) * Doctrine: Allow mixed type value for date filter (notice if invalid) (#3870) * MongoDB: `date_immutable` support (#3940) +* Add `TraversablePaginator` (#3783) ## 2.6.3 diff --git a/src/DataProvider/TraversablePaginator.php b/src/DataProvider/TraversablePaginator.php new file mode 100644 index 00000000000..91635bc1fb4 --- /dev/null +++ b/src/DataProvider/TraversablePaginator.php @@ -0,0 +1,90 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace ApiPlatform\Core\DataProvider; + +final class TraversablePaginator implements \IteratorAggregate, PaginatorInterface +{ + private $traversable; + private $currentPage; + private $itemsPerPage; + private $totalItems; + + public function __construct(\Traversable $iterator, float $currentPage, float $itemsPerPage, float $totalItems) + { + $this->traversable = $iterator; + $this->currentPage = $currentPage; + $this->itemsPerPage = $itemsPerPage; + $this->totalItems = $totalItems; + } + + /** + * {@inheritdoc} + */ + public function getCurrentPage(): float + { + return $this->currentPage; + } + + /** + * {@inheritdoc} + */ + public function getLastPage(): float + { + if (0. >= $this->itemsPerPage) { + return 1.; + } + + return max(ceil($this->totalItems / $this->itemsPerPage) ?: 1., 1.); + } + + /** + * {@inheritdoc} + */ + public function getItemsPerPage(): float + { + return $this->itemsPerPage; + } + + /** + * {@inheritdoc} + */ + public function getTotalItems(): float + { + return $this->totalItems; + } + + /** + * {@inheritdoc} + */ + public function count(): int + { + if ($this->getCurrentPage() < $this->getLastPage()) { + return (int) ceil($this->itemsPerPage); + } + + if (0. >= $this->itemsPerPage) { + return (int) ceil($this->totalItems); + } + + return $this->totalItems % $this->itemsPerPage; + } + + /** + * {@inheritdoc} + */ + public function getIterator(): \Traversable + { + return $this->traversable; + } +} diff --git a/tests/DataProvider/TraversablePaginatorTest.php b/tests/DataProvider/TraversablePaginatorTest.php new file mode 100644 index 00000000000..ac96262a102 --- /dev/null +++ b/tests/DataProvider/TraversablePaginatorTest.php @@ -0,0 +1,56 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace ApiPlatform\Core\Tests\DataProvider; + +use ApiPlatform\Core\DataProvider\TraversablePaginator; +use ArrayIterator; +use PHPUnit\Framework\TestCase; + +class TraversablePaginatorTest extends TestCase +{ + /** + * @dataProvider initializeProvider + */ + public function testInitialize( + array $results, + float $currentPage, + float $perPage, + float $totalItems, + float $lastPage, + float $currentItems + ): void { + $traversable = new ArrayIterator($results); + + $paginator = new TraversablePaginator($traversable, $currentPage, $perPage, $totalItems); + + self::assertEquals($totalItems, $paginator->getTotalItems()); + self::assertEquals($currentPage, $paginator->getCurrentPage()); + self::assertEquals($lastPage, $paginator->getLastPage()); + self::assertEquals($perPage, $paginator->getItemsPerPage()); + self::assertEquals($currentItems, $paginator->count()); + + self::assertSame($results, iterator_to_array($paginator)); + } + + public function initializeProvider(): array + { + return [ + 'First of three pages of 3 items each' => [[0, 1, 2, 3, 4, 5, 6], 1, 3, 7, 3, 3], + 'Second of two pages of 3 items for the first page and 2 for the second' => [[0, 1, 2, 3, 4], 2, 3, 5, 2, 2], + 'Empty results' => [[], 1, 2, 0, 1, 0], + '0 items per page' => [[0, 1, 2, 3], 1, 0, 4, 1, 4], + 'Total items less than items per page' => [[0, 1, 2], 1, 4, 3, 1, 3], + ]; + } +}