Skip to content

Commit 092a3a4

Browse files
authored
feat(mongodb): partial paginator (#7352)
1 parent 26d2394 commit 092a3a4

File tree

6 files changed

+284
-51
lines changed

6 files changed

+284
-51
lines changed
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
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\Odm;
15+
16+
use ApiPlatform\State\Pagination\PartialPaginatorInterface;
17+
18+
abstract class AbstractPaginator implements \IteratorAggregate, PartialPaginatorInterface
19+
{
20+
protected int $firstResult;
21+
protected int $maxResults;
22+
protected \ArrayIterator $iterator;
23+
protected int $count;
24+
25+
public function __construct(array $result)
26+
{
27+
$this->firstResult = $result['__api_first_result__'];
28+
$this->maxResults = $result['__api_max_results__'];
29+
}
30+
31+
/**
32+
* {@inheritdoc}
33+
*/
34+
public function getCurrentPage(): float
35+
{
36+
if (0 >= $this->maxResults) {
37+
return 1.;
38+
}
39+
40+
return floor($this->firstResult / $this->maxResults) + 1.;
41+
}
42+
43+
/**
44+
* {@inheritdoc}
45+
*/
46+
public function getItemsPerPage(): float
47+
{
48+
return (float) $this->maxResults;
49+
}
50+
51+
/**
52+
* {@inheritdoc}
53+
*/
54+
public function getIterator(): \Traversable
55+
{
56+
return $this->iterator;
57+
}
58+
59+
/**
60+
* {@inheritdoc}
61+
*/
62+
public function count(): int
63+
{
64+
return $this->count;
65+
}
66+
}

src/Doctrine/Odm/Extension/PaginationExtension.php

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
namespace ApiPlatform\Doctrine\Odm\Extension;
1515

1616
use ApiPlatform\Doctrine\Odm\Paginator;
17+
use ApiPlatform\Doctrine\Odm\PartialPaginator;
1718
use ApiPlatform\Metadata\Exception\RuntimeException;
1819
use ApiPlatform\Metadata\Operation;
1920
use ApiPlatform\State\Pagination\Pagination;
@@ -50,7 +51,9 @@ public function applyToCollection(Builder $aggregationBuilder, string $resourceC
5051
return;
5152
}
5253

53-
$context = $this->addCountToContext(clone $aggregationBuilder, $context);
54+
if ($doesCount = !$this->pagination->isPartialEnabled($operation, $context)) {
55+
$context = $this->addCountToContext(clone $aggregationBuilder, $context);
56+
}
5457

5558
[, $offset, $limit] = $this->pagination->getPagination($operation, $context);
5659

@@ -76,7 +79,9 @@ public function applyToCollection(Builder $aggregationBuilder, string $resourceC
7679
}
7780

7881
// Count the total number of items
79-
$facet->field('count')->pipeline($repository->createAggregationBuilder()->count('count'));
82+
if ($doesCount) {
83+
$facet->field('count')->pipeline($repository->createAggregationBuilder()->count('count'));
84+
}
8085

8186
// Store pagination metadata, read by the Paginator
8287
// Using __ to avoid field names mapping
@@ -111,7 +116,13 @@ public function getResult(Builder $aggregationBuilder, string $resourceClass, ?O
111116
$attribute = $operation?->getExtraProperties()['doctrine_mongodb'] ?? [];
112117
$executeOptions = $attribute['execute_options'] ?? [];
113118

114-
return new Paginator($aggregationBuilder->getAggregation($executeOptions)->getIterator(), $manager->getUnitOfWork(), $resourceClass);
119+
$iterator = $aggregationBuilder->getAggregation($executeOptions)->getIterator();
120+
121+
if ($this->pagination->isPartialEnabled($operation, $context)) {
122+
return new PartialPaginator($iterator, $manager->getUnitOfWork(), $resourceClass);
123+
}
124+
125+
return new Paginator($iterator, $manager->getUnitOfWork(), $resourceClass);
115126
}
116127

117128
private function addCountToContext(Builder $aggregationBuilder, array $context): array

src/Doctrine/Odm/Paginator.php

Lines changed: 3 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -25,18 +25,10 @@
2525
* @author Kévin Dunglas <[email protected]>
2626
* @author Alan Poulain <[email protected]>
2727
*/
28-
final class Paginator implements \IteratorAggregate, PaginatorInterface, HasNextPagePaginatorInterface
28+
final class Paginator extends AbstractPaginator implements PaginatorInterface, HasNextPagePaginatorInterface
2929
{
30-
private readonly \ArrayIterator $iterator;
31-
32-
private readonly int $firstResult;
33-
34-
private readonly int $maxResults;
35-
3630
private readonly int $totalItems;
3731

38-
private readonly int $count;
39-
4032
public function __construct(Iterator $mongoDbOdmIterator, UnitOfWork $unitOfWork, string $resourceClass)
4133
{
4234
$result = $mongoDbOdmIterator->toArray()[0];
@@ -45,6 +37,8 @@ public function __construct(Iterator $mongoDbOdmIterator, UnitOfWork $unitOfWork
4537
throw new RuntimeException('The result of the query must contain only "__api_first_result__", "__api_max_results__", "results" and "count" fields.');
4638
}
4739

40+
parent::__construct($result);
41+
4842
// The "count" facet contains the total number of documents,
4943
// it is not set when the query does not return any document
5044
$this->totalItems = $result['count'][0]['count'] ?? 0;
@@ -60,21 +54,6 @@ public function __construct(Iterator $mongoDbOdmIterator, UnitOfWork $unitOfWork
6054
$result['results'],
6155
));
6256
}
63-
64-
$this->firstResult = $result['__api_first_result__'];
65-
$this->maxResults = $result['__api_max_results__'];
66-
}
67-
68-
/**
69-
* {@inheritdoc}
70-
*/
71-
public function getCurrentPage(): float
72-
{
73-
if (0 >= $this->maxResults) {
74-
return 1.;
75-
}
76-
77-
return floor($this->firstResult / $this->maxResults) + 1.;
7857
}
7958

8059
/**
@@ -89,14 +68,6 @@ public function getLastPage(): float
8968
return ceil($this->totalItems / $this->maxResults) ?: 1.;
9069
}
9170

92-
/**
93-
* {@inheritdoc}
94-
*/
95-
public function getItemsPerPage(): float
96-
{
97-
return (float) $this->maxResults;
98-
}
99-
10071
/**
10172
* {@inheritdoc}
10273
*/
@@ -105,22 +76,6 @@ public function getTotalItems(): float
10576
return (float) $this->totalItems;
10677
}
10778

108-
/**
109-
* {@inheritdoc}
110-
*/
111-
public function getIterator(): \Traversable
112-
{
113-
return $this->iterator;
114-
}
115-
116-
/**
117-
* {@inheritdoc}
118-
*/
119-
public function count(): int
120-
{
121-
return $this->count;
122-
}
123-
12479
/**
12580
* {@inheritdoc}
12681
*/
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
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\Odm;
15+
16+
use ApiPlatform\Metadata\Exception\RuntimeException;
17+
use Doctrine\ODM\MongoDB\Iterator\Iterator;
18+
use Doctrine\ODM\MongoDB\UnitOfWork;
19+
20+
final class PartialPaginator extends AbstractPaginator
21+
{
22+
public function __construct(Iterator $mongoDbOdmIterator, UnitOfWork $unitOfWork, string $resourceClass)
23+
{
24+
$result = $mongoDbOdmIterator->toArray()[0];
25+
26+
if (array_diff_key(['results' => 1, '__api_first_result__' => 1, '__api_max_results__' => 1], $result)) {
27+
throw new RuntimeException('The result of the query must contain only "__api_first_result__", "__api_max_results__" and "results" fields.');
28+
}
29+
30+
parent::__construct($result);
31+
32+
// The "results" facet contains the returned documents
33+
if ([] === $result['results']) {
34+
$this->count = 0;
35+
$this->iterator = new \ArrayIterator();
36+
} else {
37+
$this->count = \count($result['results']);
38+
$this->iterator = new \ArrayIterator(array_map(
39+
static fn ($result): object => $unitOfWork->getOrCreateDocument($resourceClass, $result),
40+
$result['results'],
41+
));
42+
}
43+
}
44+
}
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
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\Tests\Fixtures\TestBundle\Document;
15+
16+
use ApiPlatform\Metadata\ApiResource;
17+
use ApiPlatform\Metadata\GetCollection;
18+
use Doctrine\ODM\MongoDB\Mapping\Annotations as ODM;
19+
20+
#[ApiResource(
21+
operations: [
22+
new GetCollection(
23+
normalizationContext: ['hydra_prefix' => false],
24+
paginationPartial: true,
25+
paginationItemsPerPage: 3
26+
),
27+
]
28+
)]
29+
#[ODM\Document]
30+
class PartialPaginationDummy
31+
{
32+
#[ODM\Id(strategy: 'INCREMENT')]
33+
private ?int $id = null;
34+
35+
#[ODM\Field(type: 'string')]
36+
private ?string $name = null;
37+
38+
public function getId(): ?int
39+
{
40+
return $this->id;
41+
}
42+
43+
public function getName(): ?string
44+
{
45+
return $this->name;
46+
}
47+
48+
public function setName(string $name): self
49+
{
50+
$this->name = $name;
51+
52+
return $this;
53+
}
54+
}

0 commit comments

Comments
 (0)