Skip to content

Commit e6759d1

Browse files
committed
feat: paginators for Doctrine Collection & Selectable
1 parent 8a35ee2 commit e6759d1

6 files changed

+418
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the API Platform project.
5+
*
6+
* (c) Kévin Dunglas <[email protected]>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
declare(strict_types=1);
13+
14+
namespace ApiPlatform\Doctrine\Common;
15+
16+
use ApiPlatform\State\Pagination\PaginatorInterface;
17+
use Doctrine\Common\Collections\ReadableCollection;
18+
19+
/**
20+
* @template T of object
21+
* @implements PaginatorInterface<T>
22+
* @implements \IteratorAggregate<T>
23+
*/
24+
final readonly class CollectionPaginator implements \IteratorAggregate, PaginatorInterface
25+
{
26+
/**
27+
* @var array<array-key,T>
28+
*/
29+
private readonly array $items;
30+
private readonly float $totalItems;
31+
32+
/**
33+
* @param ReadableCollection<array-key,T> $collection
34+
*/
35+
public function __construct(
36+
readonly ReadableCollection $collection,
37+
private readonly float $currentPage,
38+
private readonly float $itemsPerPage
39+
) {
40+
$this->items = $collection->slice((int)(($currentPage - 1) * $itemsPerPage), $itemsPerPage > 0 ? (int) $itemsPerPage : null);
41+
$this->totalItems = $collection->count();
42+
}
43+
44+
/**
45+
* {@inheritdoc}
46+
*/
47+
public function getCurrentPage(): float
48+
{
49+
return $this->currentPage;
50+
}
51+
52+
/**
53+
* {@inheritdoc}
54+
*/
55+
public function getLastPage(): float
56+
{
57+
if (0. >= $this->itemsPerPage) {
58+
return 1.;
59+
}
60+
61+
return max(ceil($this->totalItems / $this->itemsPerPage) ?: 1., 1.);
62+
}
63+
64+
/**
65+
* {@inheritdoc}
66+
*/
67+
public function getItemsPerPage(): float
68+
{
69+
return $this->itemsPerPage;
70+
}
71+
72+
/**
73+
* {@inheritdoc}
74+
*/
75+
public function getTotalItems(): float
76+
{
77+
return $this->totalItems;
78+
}
79+
80+
/**
81+
* {@inheritdoc}
82+
*/
83+
public function count(): int
84+
{
85+
return \count($this->items);
86+
}
87+
88+
/**
89+
* {@inheritdoc}
90+
* @return \Traversable<T>
91+
*/
92+
public function getIterator(): \Traversable
93+
{
94+
yield from $this->items;
95+
}
96+
}
+103
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the API Platform project.
5+
*
6+
* (c) Kévin Dunglas <[email protected]>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
declare(strict_types=1);
13+
14+
namespace ApiPlatform\Doctrine\Common;
15+
16+
use ApiPlatform\State\Pagination\PaginatorInterface;
17+
use Doctrine\Common\Collections\Criteria;
18+
use Doctrine\Common\Collections\ReadableCollection;
19+
use Doctrine\Common\Collections\Selectable;
20+
21+
/**
22+
* @template T of object
23+
* @implements PaginatorInterface<T>
24+
* @implements \IteratorAggregate<T>
25+
*/
26+
final readonly class SelectablePaginator implements \IteratorAggregate, PaginatorInterface
27+
{
28+
/**
29+
* @var ReadableCollection<array-key,T>
30+
*/
31+
private readonly ReadableCollection $slicedCollection;
32+
private readonly float $totalItems;
33+
34+
/**
35+
* @param Selectable<array-key,T> $selectable
36+
*/
37+
public function __construct(
38+
readonly Selectable $selectable,
39+
private readonly float $currentPage,
40+
private readonly float $itemsPerPage
41+
) {
42+
$this->totalItems = $this->selectable instanceof \Countable ? $this->selectable->count() : $this->selectable->matching(Criteria::create())->count();
43+
44+
$criteria = Criteria::create()
45+
->setFirstResult((int) (($currentPage - 1) * $itemsPerPage))
46+
->setMaxResults($itemsPerPage > 0 ? (int) $itemsPerPage : null);
47+
48+
$this->slicedCollection = $selectable->matching($criteria);
49+
}
50+
51+
/**
52+
* {@inheritdoc}
53+
*/
54+
public function getCurrentPage(): float
55+
{
56+
return $this->currentPage;
57+
}
58+
59+
/**
60+
* {@inheritdoc}
61+
*/
62+
public function getLastPage(): float
63+
{
64+
if (0. >= $this->itemsPerPage) {
65+
return 1.;
66+
}
67+
68+
return max(ceil($this->totalItems / $this->itemsPerPage) ?: 1., 1.);
69+
}
70+
71+
/**
72+
* {@inheritdoc}
73+
*/
74+
public function getItemsPerPage(): float
75+
{
76+
return $this->itemsPerPage;
77+
}
78+
79+
/**
80+
* {@inheritdoc}
81+
*/
82+
public function getTotalItems(): float
83+
{
84+
return $this->totalItems;
85+
}
86+
87+
/**
88+
* {@inheritdoc}
89+
*/
90+
public function count(): int
91+
{
92+
return $this->slicedCollection->count();
93+
}
94+
95+
/**
96+
* {@inheritdoc}
97+
* @return \Traversable<T>
98+
*/
99+
public function getIterator(): \Traversable
100+
{
101+
return $this->slicedCollection->getIterator();
102+
}
103+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the API Platform project.
5+
*
6+
* (c) Kévin Dunglas <[email protected]>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
declare(strict_types=1);
13+
14+
namespace ApiPlatform\Doctrine\Common;
15+
16+
use ApiPlatform\State\Pagination\PartialPaginatorInterface;
17+
use Doctrine\Common\Collections\Criteria;
18+
use Doctrine\Common\Collections\ReadableCollection;
19+
use Doctrine\Common\Collections\Selectable;
20+
21+
/**
22+
* @template T of object
23+
* @implements PartialPaginatorInterface<T>
24+
* @implements \IteratorAggregate<T>
25+
*/
26+
final readonly class SelectablePartialPaginator implements \IteratorAggregate,PartialPaginatorInterface
27+
{
28+
/**
29+
* @var ReadableCollection<array-key,T>
30+
*/
31+
private readonly ReadableCollection $slicedCollection;
32+
33+
/**
34+
* @param Selectable<array-key,T> $selectable
35+
*/
36+
public function __construct(
37+
readonly Selectable $selectable,
38+
private readonly float $currentPage,
39+
private readonly float $itemsPerPage
40+
) {
41+
$criteria = Criteria::create()
42+
->setFirstResult((int) (($currentPage - 1) * $itemsPerPage))
43+
->setMaxResults((int) $itemsPerPage);
44+
45+
$this->slicedCollection = $selectable->matching($criteria);
46+
}
47+
48+
/**
49+
* {@inheritdoc}
50+
*/
51+
public function getCurrentPage(): float
52+
{
53+
return $this->currentPage;
54+
}
55+
56+
/**
57+
* {@inheritdoc}
58+
*/
59+
public function getItemsPerPage(): float
60+
{
61+
return $this->itemsPerPage;
62+
}
63+
64+
/**
65+
* {@inheritdoc}
66+
*/
67+
public function count(): int
68+
{
69+
return $this->slicedCollection->count();
70+
}
71+
72+
/**
73+
* {@inheritdoc}
74+
* @return \Traversable<T>
75+
*/
76+
public function getIterator(): \Traversable
77+
{
78+
return $this->slicedCollection->getIterator();
79+
}
80+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the API Platform project.
5+
*
6+
* (c) Kévin Dunglas <[email protected]>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
declare(strict_types=1);
13+
14+
namespace ApiPlatform\Doctrine\Common\Tests;
15+
16+
use ApiPlatform\Doctrine\Common\CollectionPaginator;
17+
use Doctrine\Common\Collections\ArrayCollection;
18+
use PHPUnit\Framework\TestCase;
19+
20+
class CollectionPaginatorTest extends TestCase
21+
{
22+
/**
23+
* @dataProvider initializeProvider
24+
*/
25+
public function testInitialize($results, $currentPage, $itemsPerPage, $totalItems, $lastPage, $currentItems): void
26+
{
27+
$results = new ArrayCollection($results);
28+
$paginator = new CollectionPaginator($results, $currentPage, $itemsPerPage);
29+
30+
$this->assertSame((float) $currentPage, $paginator->getCurrentPage());
31+
$this->assertSame((float) $itemsPerPage, $paginator->getItemsPerPage());
32+
$this->assertSame((float) $totalItems, $paginator->getTotalItems());
33+
$this->assertCount($currentItems, $paginator);
34+
$this->assertSame((float) $lastPage, $paginator->getLastPage());
35+
}
36+
37+
public static function initializeProvider(): array
38+
{
39+
return [
40+
'First of three pages of 3 items each' => [[0, 1, 2, 3, 4, 5, 6], 1, 3, 7, 3, 3],
41+
'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],
42+
'Empty results' => [[], 1, 2, 0, 1, 0],
43+
'0 items per page' => [[0, 1, 2, 3], 1, 0, 4, 1, 4],
44+
'Total items less than items per page' => [[0, 1, 2], 1, 4, 3, 1, 3],
45+
];
46+
}
47+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the API Platform project.
5+
*
6+
* (c) Kévin Dunglas <[email protected]>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
declare(strict_types=1);
13+
14+
namespace ApiPlatform\Doctrine\Common\Tests;
15+
16+
use ApiPlatform\Doctrine\Common\SelectablePaginator;
17+
use Doctrine\Common\Collections\ArrayCollection;
18+
use PHPUnit\Framework\TestCase;
19+
20+
class SelectablePaginatorTest extends TestCase
21+
{
22+
/**
23+
* @dataProvider initializeProvider
24+
*/
25+
public function testInitialize($results, $currentPage, $itemsPerPage, $totalItems, $lastPage, $currentItems): void
26+
{
27+
$results = new ArrayCollection($results);
28+
$paginator = new SelectablePaginator($results, $currentPage, $itemsPerPage);
29+
30+
$this->assertSame((float) $currentPage, $paginator->getCurrentPage());
31+
$this->assertSame((float) $itemsPerPage, $paginator->getItemsPerPage());
32+
$this->assertSame((float) $totalItems, $paginator->getTotalItems());
33+
$this->assertCount($currentItems, $paginator);
34+
$this->assertSame((float) $lastPage, $paginator->getLastPage());
35+
}
36+
37+
public static function initializeProvider(): array
38+
{
39+
return [
40+
'First of three pages of 3 items each' => [[0, 1, 2, 3, 4, 5, 6], 1, 3, 7, 3, 3],
41+
'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],
42+
'Empty results' => [[], 1, 2, 0, 1, 0],
43+
'0 items per page' => [[0, 1, 2, 3], 1, 0, 4, 1, 4],
44+
'Total items less than items per page' => [[0, 1, 2], 1, 4, 3, 1, 3],
45+
];
46+
}
47+
}

0 commit comments

Comments
 (0)