Passed
Pull Request — master (#2144)
by Alan
03:37
created

PaginationExtension::isPaginationEnabled()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 10
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 5
nc 2
nop 3
dl 0
loc 10
rs 10
c 0
b 0
f 0
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
 * This file is part of the API Platform project.
15
 *
16
 * (c) Kévin Dunglas <[email protected]>
17
 *
18
 * For the full copyright and license information, please view the LICENSE
19
 * file that was distributed with this source code.
20
 */
21
22
namespace ApiPlatform\Core\Bridge\Doctrine\MongoDbOdm\Extension;
23
24
use ApiPlatform\Core\Bridge\Doctrine\MongoDbOdm\Paginator;
25
use ApiPlatform\Core\Exception\InvalidArgumentException;
26
use ApiPlatform\Core\Metadata\Resource\Factory\ResourceMetadataFactoryInterface;
27
use ApiPlatform\Core\Metadata\Resource\ResourceMetadata;
28
use Doctrine\Common\Persistence\ManagerRegistry;
29
use Doctrine\ODM\MongoDB\Aggregation\Builder;
30
use Symfony\Component\HttpFoundation\Request;
31
use Symfony\Component\HttpFoundation\RequestStack;
32
33
/**
34
 * Applies pagination on the Doctrine aggregation for resource collection when enabled.
35
 *
36
 * @experimental
37
 *
38
 * @author Kévin Dunglas <[email protected]>
39
 * @author Samuel ROZE <[email protected]>
40
 * @author Alan Poulain <[email protected]>
41
 */
42
final class PaginationExtension implements AggregationResultCollectionExtensionInterface
43
{
44
    private $managerRegistry;
45
    private $requestStack;
46
    private $resourceMetadataFactory;
47
    private $enabled;
48
    private $clientEnabled;
49
    private $clientItemsPerPage;
50
    private $itemsPerPage;
51
    private $pageParameterName;
52
    private $enabledParameterName;
53
    private $itemsPerPageParameterName;
54
    private $maximumItemPerPage;
55
56
    public function __construct(ManagerRegistry $managerRegistry, RequestStack $requestStack, ResourceMetadataFactoryInterface $resourceMetadataFactory, bool $enabled = true, bool $clientEnabled = false, bool $clientItemsPerPage = false, int $itemsPerPage = 30, string $pageParameterName = 'page', string $enabledParameterName = 'pagination', string $itemsPerPageParameterName = 'itemsPerPage', int $maximumItemPerPage = null)
57
    {
58
        $this->managerRegistry = $managerRegistry;
59
        $this->requestStack = $requestStack;
60
        $this->resourceMetadataFactory = $resourceMetadataFactory;
61
        $this->enabled = $enabled;
62
        $this->clientEnabled = $clientEnabled;
63
        $this->clientItemsPerPage = $clientItemsPerPage;
64
        $this->itemsPerPage = $itemsPerPage;
65
        $this->pageParameterName = $pageParameterName;
66
        $this->enabledParameterName = $enabledParameterName;
67
        $this->itemsPerPageParameterName = $itemsPerPageParameterName;
68
        $this->maximumItemPerPage = $maximumItemPerPage;
69
    }
70
71
    /**
72
     * {@inheritdoc}
73
     */
74
    public function applyToCollection(Builder $aggregationBuilder, string $resourceClass, string $operationName = null, array &$context = [])
75
    {
76
        $request = $this->requestStack->getCurrentRequest();
77
        if (null === $request) {
78
            return;
79
        }
80
81
        $resourceMetadata = $this->resourceMetadataFactory->create($resourceClass);
82
        if (!$this->isPaginationEnabled($request, $resourceMetadata, $operationName)) {
83
            return;
84
        }
85
86
        $itemsPerPage = $resourceMetadata->getCollectionOperationAttribute($operationName, 'pagination_items_per_page', $this->itemsPerPage, true);
87
        if ($request->attributes->get('_graphql')) {
88
            $collectionArgs = $request->attributes->get('_graphql_collections_args', []);
89
            $itemsPerPage = $collectionArgs[$resourceClass]['first'] ?? $itemsPerPage;
90
        }
91
92
        if ($resourceMetadata->getCollectionOperationAttribute($operationName, 'pagination_client_items_per_page', $this->clientItemsPerPage, true)) {
93
            $maxItemsPerPage = $resourceMetadata->getCollectionOperationAttribute($operationName, 'maximum_items_per_page', $this->maximumItemPerPage, true);
94
95
            $itemsPerPage = (int) $this->getPaginationParameter($request, $this->itemsPerPageParameterName, $itemsPerPage);
96
            $itemsPerPage = (null !== $maxItemsPerPage && $itemsPerPage >= $maxItemsPerPage ? $maxItemsPerPage : $itemsPerPage);
97
        }
98
99
        if (0 >= $itemsPerPage) {
100
            throw new InvalidArgumentException('Item per page parameter should not be less than 1');
101
        }
102
103
        $page = (int) $this->getPaginationParameter($request, $this->pageParameterName, 1);
104
105
        if (1 > $page) {
106
            throw new InvalidArgumentException('Page should not be less than 1');
107
        }
108
109
        $firstResult = ($page - 1) * $itemsPerPage;
110
        if ($request->attributes->get('_graphql')) {
111
            $collectionArgs = $request->attributes->get('_graphql_collections_args', []);
112
            if (isset($collectionArgs[$resourceClass]['after'])) {
113
                $after = \base64_decode($collectionArgs[$resourceClass]['after'], true);
114
                $firstResult = (int) $after;
115
                $firstResult = false === $after ? $firstResult : ++$firstResult;
116
            }
117
        }
118
119
        $repository = $this->managerRegistry->getManagerForClass($resourceClass)->getRepository($resourceClass);
120
        $aggregationBuilder
121
            ->facet()
122
            ->field('results')->pipeline(
123
                $repository->createAggregationBuilder()
0 ignored issues
show
Bug introduced by
The method createAggregationBuilder() does not exist on Doctrine\Common\Persistence\ObjectRepository. It seems like you code against a sub-type of said class. However, the method does not exist in Doctrine\ODM\MongoDB\Repository\GridFSRepository. Are you sure you never get one of those? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

123
                $repository->/** @scrutinizer ignore-call */ 
124
                             createAggregationBuilder()
Loading history...
124
                    ->skip($firstResult)
125
                    ->limit($itemsPerPage)
126
            )
127
            ->field('count')->pipeline(
128
                $repository->createAggregationBuilder()
129
                    ->count('count')
130
            );
131
    }
132
133
    /**
134
     * {@inheritdoc}
135
     */
136
    public function supportsResult(string $resourceClass, string $operationName = null, array $context = []): bool
137
    {
138
        $request = $this->requestStack->getCurrentRequest();
139
        if (null === $request) {
140
            return false;
141
        }
142
143
        $resourceMetadata = $this->resourceMetadataFactory->create($resourceClass);
144
145
        return $this->isPaginationEnabled($request, $resourceMetadata, $operationName);
146
    }
147
148
    /**
149
     * {@inheritdoc}
150
     */
151
    public function getResult(Builder $aggregationBuilder, string $resourceClass, string $operationName = null, array $context = [])
152
    {
153
        return new Paginator($aggregationBuilder->execute(), $this->managerRegistry->getManagerForClass($resourceClass)->getUnitOfWork(), $resourceClass, $aggregationBuilder->getPipeline());
0 ignored issues
show
Bug introduced by
The method getUnitOfWork() does not exist on Doctrine\Common\Persistence\ObjectManager. It seems like you code against a sub-type of said class. However, the method does not exist in Doctrine\Common\Persistence\ObjectManagerDecorator. Are you sure you never get one of those? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

153
        return new Paginator($aggregationBuilder->execute(), $this->managerRegistry->getManagerForClass($resourceClass)->/** @scrutinizer ignore-call */ getUnitOfWork(), $resourceClass, $aggregationBuilder->getPipeline());
Loading history...
154
    }
155
156
    private function isPaginationEnabled(Request $request, ResourceMetadata $resourceMetadata, string $operationName = null): bool
157
    {
158
        $enabled = $resourceMetadata->getCollectionOperationAttribute($operationName, 'pagination_enabled', $this->enabled, true);
159
        $clientEnabled = $resourceMetadata->getCollectionOperationAttribute($operationName, 'pagination_client_enabled', $this->clientEnabled, true);
160
161
        if ($clientEnabled) {
162
            $enabled = filter_var($request->query->get($this->enabledParameterName, $enabled), FILTER_VALIDATE_BOOLEAN);
163
        }
164
165
        return $enabled;
166
    }
167
168
    private function getPaginationParameter(Request $request, string $parameterName, $default = null)
169
    {
170
        if (null !== $paginationAttribute = $request->attributes->get('_api_pagination')) {
171
            return array_key_exists($parameterName, $paginationAttribute) ? $paginationAttribute[$parameterName] : $default;
172
        }
173
174
        return $request->query->get($parameterName, $default);
175
    }
176
}
177