Skip to content

Commit 79fe01b

Browse files
authored
feat(doctrine): paginators for Doctrine Collection & Selectable (#6153)
1 parent d1c1a3b commit 79fe01b

6 files changed

+424
-0
lines changed

Diff for: src/Doctrine/Common/CollectionPaginator.php

+98
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
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+
*
22+
* @implements PaginatorInterface<T>
23+
* @implements \IteratorAggregate<T>
24+
*/
25+
final class CollectionPaginator implements \IteratorAggregate, PaginatorInterface
26+
{
27+
/**
28+
* @var array<array-key,T>
29+
*/
30+
private readonly array $items;
31+
private readonly float $totalItems;
32+
33+
/**
34+
* @param ReadableCollection<array-key,T> $collection
35+
*/
36+
public function __construct(
37+
readonly ReadableCollection $collection,
38+
private readonly float $currentPage,
39+
private readonly float $itemsPerPage
40+
) {
41+
$this->items = $collection->slice((int) (($currentPage - 1) * $itemsPerPage), $itemsPerPage > 0 ? (int) $itemsPerPage : null);
42+
$this->totalItems = $collection->count();
43+
}
44+
45+
/**
46+
* {@inheritdoc}
47+
*/
48+
public function getCurrentPage(): float
49+
{
50+
return $this->currentPage;
51+
}
52+
53+
/**
54+
* {@inheritdoc}
55+
*/
56+
public function getLastPage(): float
57+
{
58+
if (0. >= $this->itemsPerPage) {
59+
return 1.;
60+
}
61+
62+
return max(ceil($this->totalItems / $this->itemsPerPage) ?: 1., 1.);
63+
}
64+
65+
/**
66+
* {@inheritdoc}
67+
*/
68+
public function getItemsPerPage(): float
69+
{
70+
return $this->itemsPerPage;
71+
}
72+
73+
/**
74+
* {@inheritdoc}
75+
*/
76+
public function getTotalItems(): float
77+
{
78+
return $this->totalItems;
79+
}
80+
81+
/**
82+
* {@inheritdoc}
83+
*/
84+
public function count(): int
85+
{
86+
return \count($this->items);
87+
}
88+
89+
/**
90+
* {@inheritdoc}
91+
*
92+
* @return \Traversable<T>
93+
*/
94+
public function getIterator(): \Traversable
95+
{
96+
yield from $this->items;
97+
}
98+
}

Diff for: src/Doctrine/Common/SelectablePaginator.php

+105
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
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+
*
24+
* @implements PaginatorInterface<T>
25+
* @implements \IteratorAggregate<T>
26+
*/
27+
final class SelectablePaginator implements \IteratorAggregate, PaginatorInterface
28+
{
29+
/**
30+
* @var ReadableCollection<array-key,T>
31+
*/
32+
private readonly ReadableCollection $slicedCollection;
33+
private readonly float $totalItems;
34+
35+
/**
36+
* @param Selectable<array-key,T> $selectable
37+
*/
38+
public function __construct(
39+
readonly Selectable $selectable,
40+
private readonly float $currentPage,
41+
private readonly float $itemsPerPage
42+
) {
43+
$this->totalItems = $this->selectable instanceof \Countable ? $this->selectable->count() : $this->selectable->matching(Criteria::create())->count();
44+
45+
$criteria = Criteria::create()
46+
->setFirstResult((int) (($currentPage - 1) * $itemsPerPage))
47+
->setMaxResults($itemsPerPage > 0 ? (int) $itemsPerPage : null);
48+
49+
$this->slicedCollection = $selectable->matching($criteria);
50+
}
51+
52+
/**
53+
* {@inheritdoc}
54+
*/
55+
public function getCurrentPage(): float
56+
{
57+
return $this->currentPage;
58+
}
59+
60+
/**
61+
* {@inheritdoc}
62+
*/
63+
public function getLastPage(): float
64+
{
65+
if (0. >= $this->itemsPerPage) {
66+
return 1.;
67+
}
68+
69+
return max(ceil($this->totalItems / $this->itemsPerPage) ?: 1., 1.);
70+
}
71+
72+
/**
73+
* {@inheritdoc}
74+
*/
75+
public function getItemsPerPage(): float
76+
{
77+
return $this->itemsPerPage;
78+
}
79+
80+
/**
81+
* {@inheritdoc}
82+
*/
83+
public function getTotalItems(): float
84+
{
85+
return $this->totalItems;
86+
}
87+
88+
/**
89+
* {@inheritdoc}
90+
*/
91+
public function count(): int
92+
{
93+
return $this->slicedCollection->count();
94+
}
95+
96+
/**
97+
* {@inheritdoc}
98+
*
99+
* @return \Traversable<T>
100+
*/
101+
public function getIterator(): \Traversable
102+
{
103+
return $this->slicedCollection->getIterator();
104+
}
105+
}

Diff for: src/Doctrine/Common/SelectablePartialPaginator.php

+82
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
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+
*
24+
* @implements PartialPaginatorInterface<T>
25+
* @implements \IteratorAggregate<T>
26+
*/
27+
final class SelectablePartialPaginator implements \IteratorAggregate, PartialPaginatorInterface
28+
{
29+
/**
30+
* @var ReadableCollection<array-key,T>
31+
*/
32+
private readonly ReadableCollection $slicedCollection;
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+
$criteria = Criteria::create()
43+
->setFirstResult((int) (($currentPage - 1) * $itemsPerPage))
44+
->setMaxResults((int) $itemsPerPage);
45+
46+
$this->slicedCollection = $selectable->matching($criteria);
47+
}
48+
49+
/**
50+
* {@inheritdoc}
51+
*/
52+
public function getCurrentPage(): float
53+
{
54+
return $this->currentPage;
55+
}
56+
57+
/**
58+
* {@inheritdoc}
59+
*/
60+
public function getItemsPerPage(): float
61+
{
62+
return $this->itemsPerPage;
63+
}
64+
65+
/**
66+
* {@inheritdoc}
67+
*/
68+
public function count(): int
69+
{
70+
return $this->slicedCollection->count();
71+
}
72+
73+
/**
74+
* {@inheritdoc}
75+
*
76+
* @return \Traversable<T>
77+
*/
78+
public function getIterator(): \Traversable
79+
{
80+
return $this->slicedCollection->getIterator();
81+
}
82+
}
+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\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+
}
+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)