Skip to content

Commit

Permalink
feat: paginators for Doctrine Collection & Selectable
Browse files Browse the repository at this point in the history
  • Loading branch information
priyadi committed Feb 11, 2024
1 parent 8a35ee2 commit 6da691b
Show file tree
Hide file tree
Showing 6 changed files with 424 additions and 0 deletions.
98 changes: 98 additions & 0 deletions src/Doctrine/Common/CollectionPaginator.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
<?php

/*
* This file is part of the API Platform project.
*
* (c) Kévin Dunglas <[email protected]>
*
* 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\Doctrine\Common;

use ApiPlatform\State\Pagination\PaginatorInterface;
use Doctrine\Common\Collections\ReadableCollection;

/**
* @template T of object
*
* @implements PaginatorInterface<T>
* @implements \IteratorAggregate<T>
*/
final class CollectionPaginator implements \IteratorAggregate, PaginatorInterface
{
/**
* @var array<array-key,T>
*/
private readonly array $items;
private readonly float $totalItems;

/**
* @param ReadableCollection<array-key,T> $collection
*/
public function __construct(
readonly ReadableCollection $collection,
private readonly float $currentPage,
private readonly float $itemsPerPage
) {
$this->items = $collection->slice((int) (($currentPage - 1) * $itemsPerPage), $itemsPerPage > 0 ? (int) $itemsPerPage : null);
$this->totalItems = $collection->count();
}

/**
* {@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
{
return \count($this->items);
}

/**
* {@inheritdoc}
*
* @return \Traversable<T>
*/
public function getIterator(): \Traversable

Check warning on line 94 in src/Doctrine/Common/CollectionPaginator.php

View check run for this annotation

Codecov / codecov/patch

src/Doctrine/Common/CollectionPaginator.php#L94

Added line #L94 was not covered by tests
{
yield from $this->items;

Check warning on line 96 in src/Doctrine/Common/CollectionPaginator.php

View check run for this annotation

Codecov / codecov/patch

src/Doctrine/Common/CollectionPaginator.php#L96

Added line #L96 was not covered by tests
}
}
105 changes: 105 additions & 0 deletions src/Doctrine/Common/SelectablePaginator.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
<?php

/*
* This file is part of the API Platform project.
*
* (c) Kévin Dunglas <[email protected]>
*
* 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\Doctrine\Common;

use ApiPlatform\State\Pagination\PaginatorInterface;
use Doctrine\Common\Collections\Criteria;
use Doctrine\Common\Collections\ReadableCollection;
use Doctrine\Common\Collections\Selectable;

/**
* @template T of object
*
* @implements PaginatorInterface<T>
* @implements \IteratorAggregate<T>
*/
final class SelectablePaginator implements \IteratorAggregate, PaginatorInterface
{
/**
* @var ReadableCollection<array-key,T>
*/
private readonly ReadableCollection $slicedCollection;
private readonly float $totalItems;

/**
* @param Selectable<array-key,T> $selectable
*/
public function __construct(
readonly Selectable $selectable,
private readonly float $currentPage,
private readonly float $itemsPerPage
) {
$this->totalItems = $this->selectable instanceof \Countable ? $this->selectable->count() : $this->selectable->matching(Criteria::create())->count();

$criteria = Criteria::create()
->setFirstResult((int) (($currentPage - 1) * $itemsPerPage))
->setMaxResults($itemsPerPage > 0 ? (int) $itemsPerPage : null);

$this->slicedCollection = $selectable->matching($criteria);
}

/**
* {@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
{
return $this->slicedCollection->count();
}

/**
* {@inheritdoc}
*
* @return \Traversable<T>
*/
public function getIterator(): \Traversable

Check warning on line 101 in src/Doctrine/Common/SelectablePaginator.php

View check run for this annotation

Codecov / codecov/patch

src/Doctrine/Common/SelectablePaginator.php#L101

Added line #L101 was not covered by tests
{
return $this->slicedCollection->getIterator();

Check warning on line 103 in src/Doctrine/Common/SelectablePaginator.php

View check run for this annotation

Codecov / codecov/patch

src/Doctrine/Common/SelectablePaginator.php#L103

Added line #L103 was not covered by tests
}
}
82 changes: 82 additions & 0 deletions src/Doctrine/Common/SelectablePartialPaginator.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
<?php

/*
* This file is part of the API Platform project.
*
* (c) Kévin Dunglas <[email protected]>
*
* 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\Doctrine\Common;

use ApiPlatform\State\Pagination\PartialPaginatorInterface;
use Doctrine\Common\Collections\Criteria;
use Doctrine\Common\Collections\ReadableCollection;
use Doctrine\Common\Collections\Selectable;

/**
* @template T of object
*
* @implements PartialPaginatorInterface<T>
* @implements \IteratorAggregate<T>
*/
final class SelectablePartialPaginator implements \IteratorAggregate, PartialPaginatorInterface
{
/**
* @var ReadableCollection<array-key,T>
*/
private readonly ReadableCollection $slicedCollection;

/**
* @param Selectable<array-key,T> $selectable
*/
public function __construct(
readonly Selectable $selectable,
private readonly float $currentPage,
private readonly float $itemsPerPage
) {
$criteria = Criteria::create()
->setFirstResult((int) (($currentPage - 1) * $itemsPerPage))
->setMaxResults((int) $itemsPerPage);

$this->slicedCollection = $selectable->matching($criteria);
}

/**
* {@inheritdoc}
*/
public function getCurrentPage(): float
{
return $this->currentPage;
}

/**
* {@inheritdoc}
*/
public function getItemsPerPage(): float
{
return $this->itemsPerPage;
}

/**
* {@inheritdoc}
*/
public function count(): int
{
return $this->slicedCollection->count();
}

/**
* {@inheritdoc}
*
* @return \Traversable<T>
*/
public function getIterator(): \Traversable

Check warning on line 78 in src/Doctrine/Common/SelectablePartialPaginator.php

View check run for this annotation

Codecov / codecov/patch

src/Doctrine/Common/SelectablePartialPaginator.php#L78

Added line #L78 was not covered by tests
{
return $this->slicedCollection->getIterator();

Check warning on line 80 in src/Doctrine/Common/SelectablePartialPaginator.php

View check run for this annotation

Codecov / codecov/patch

src/Doctrine/Common/SelectablePartialPaginator.php#L80

Added line #L80 was not covered by tests
}
}
47 changes: 47 additions & 0 deletions src/Doctrine/Common/Tests/CollectionPaginatorTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
<?php

/*
* This file is part of the API Platform project.
*
* (c) Kévin Dunglas <[email protected]>
*
* 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\Doctrine\Common\Tests;

use ApiPlatform\Doctrine\Common\CollectionPaginator;
use Doctrine\Common\Collections\ArrayCollection;
use PHPUnit\Framework\TestCase;

class CollectionPaginatorTest extends TestCase
{
/**
* @dataProvider initializeProvider
*/
public function testInitialize($results, $currentPage, $itemsPerPage, $totalItems, $lastPage, $currentItems): void
{
$results = new ArrayCollection($results);
$paginator = new CollectionPaginator($results, $currentPage, $itemsPerPage);

$this->assertSame((float) $currentPage, $paginator->getCurrentPage());
$this->assertSame((float) $itemsPerPage, $paginator->getItemsPerPage());
$this->assertSame((float) $totalItems, $paginator->getTotalItems());
$this->assertCount($currentItems, $paginator);
$this->assertSame((float) $lastPage, $paginator->getLastPage());
}

public static 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],
];
}
}
47 changes: 47 additions & 0 deletions src/Doctrine/Common/Tests/SelectablePaginatorTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
<?php

/*
* This file is part of the API Platform project.
*
* (c) Kévin Dunglas <[email protected]>
*
* 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\Doctrine\Common\Tests;

use ApiPlatform\Doctrine\Common\SelectablePaginator;
use Doctrine\Common\Collections\ArrayCollection;
use PHPUnit\Framework\TestCase;

class SelectablePaginatorTest extends TestCase
{
/**
* @dataProvider initializeProvider
*/
public function testInitialize($results, $currentPage, $itemsPerPage, $totalItems, $lastPage, $currentItems): void
{
$results = new ArrayCollection($results);
$paginator = new SelectablePaginator($results, $currentPage, $itemsPerPage);

$this->assertSame((float) $currentPage, $paginator->getCurrentPage());
$this->assertSame((float) $itemsPerPage, $paginator->getItemsPerPage());
$this->assertSame((float) $totalItems, $paginator->getTotalItems());
$this->assertCount($currentItems, $paginator);
$this->assertSame((float) $lastPage, $paginator->getLastPage());
}

public static 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],
];
}
}
Loading

0 comments on commit 6da691b

Please sign in to comment.