Passed
Pull Request — master (#3035)
by Alan
04:02
created

Pagination::getGraphQlEnabled()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 11
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 5
c 0
b 0
f 0
nc 2
nop 2
dl 0
loc 11
rs 10
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\Core\DataProvider;
15
16
use ApiPlatform\Core\Exception\InvalidArgumentException;
17
use ApiPlatform\Core\Metadata\Resource\Factory\ResourceMetadataFactoryInterface;
18
19
/**
20
 * Pagination configuration.
21
 *
22
 * @author Baptiste Meyer <[email protected]>
23
 */
24
final class Pagination
25
{
26
    private $options;
27
    private $graphQlOptions;
28
    private $resourceMetadataFactory;
29
30
    public function __construct(ResourceMetadataFactoryInterface $resourceMetadataFactory, array $options = [], array $graphQlOptions = [])
31
    {
32
        $this->resourceMetadataFactory = $resourceMetadataFactory;
33
        $this->options = array_merge([
34
            'enabled' => true,
35
            'client_enabled' => false,
36
            'client_items_per_page' => false,
37
            'items_per_page' => 30,
38
            'page_default' => 1,
39
            'page_parameter_name' => 'page',
40
            'enabled_parameter_name' => 'pagination',
41
            'items_per_page_parameter_name' => 'itemsPerPage',
42
            'maximum_items_per_page' => null,
43
            'partial' => false,
44
            'client_partial' => false,
45
            'partial_parameter_name' => 'partial',
46
        ], $options);
47
        $this->graphQlOptions = array_merge([
48
            'enabled' => true,
49
        ], $graphQlOptions);
50
    }
51
52
    /**
53
     * Gets the current page.
54
     *
55
     * @throws InvalidArgumentException
56
     */
57
    public function getPage(array $context = []): int
58
    {
59
        $page = (int) $this->getParameterFromContext(
60
            $context,
61
            $this->options['page_parameter_name'],
62
            $this->options['page_default']
63
        );
64
65
        if (1 > $page) {
66
            throw new InvalidArgumentException('Page should not be less than 1');
67
        }
68
69
        return $page;
70
    }
71
72
    /**
73
     * Gets the current offset.
74
     */
75
    public function getOffset(string $resourceClass = null, string $operationName = null, array $context = []): int
76
    {
77
        $graphql = (bool) ($context['graphql_operation_name'] ?? false);
78
79
        $limit = $this->getLimit($resourceClass, $operationName, $context);
80
81
        if ($graphql && null !== ($after = $this->getParameterFromContext($context, 'after'))) {
82
            return false === ($after = base64_decode($after, true)) ? 0 : (int) $after + 1;
83
        }
84
85
        if ($graphql && null !== ($before = $this->getParameterFromContext($context, 'before'))) {
86
            return ($offset = (false === ($before = base64_decode($before, true)) ? 0 : (int) $before - $limit)) < 0 ? 0 : $offset;
87
        }
88
89
        if ($graphql && null !== ($last = $this->getParameterFromContext($context, 'last'))) {
90
            return ($offset = ($context['count'] ?? 0) - $last) < 0 ? 0 : $offset;
91
        }
92
93
        return ($this->getPage($context) - 1) * $limit;
94
    }
95
96
    /**
97
     * Gets the current limit.
98
     *
99
     * @throws InvalidArgumentException
100
     */
101
    public function getLimit(string $resourceClass = null, string $operationName = null, array $context = []): int
102
    {
103
        $graphql = (bool) ($context['graphql_operation_name'] ?? false);
104
105
        $limit = $this->options['items_per_page'];
106
        $clientLimit = $this->options['client_items_per_page'];
107
108
        if (null !== $resourceClass) {
109
            $resourceMetadata = $this->resourceMetadataFactory->create($resourceClass);
110
            $limit = $resourceMetadata->getCollectionOperationAttribute($operationName, 'pagination_items_per_page', $limit, true);
111
            $clientLimit = $resourceMetadata->getCollectionOperationAttribute($operationName, 'pagination_client_items_per_page', $clientLimit, true);
112
        }
113
114
        if ($graphql && null !== ($first = $this->getParameterFromContext($context, 'first'))) {
115
            $limit = $first;
116
        }
117
118
        if ($graphql && null !== ($last = $this->getParameterFromContext($context, 'last'))) {
119
            $limit = $last;
120
        }
121
122
        if ($graphql && null !== ($before = $this->getParameterFromContext($context, 'before'))
123
            && (false === ($before = base64_decode($before, true)) ? 0 : (int) $before - $limit) < 0) {
124
            $limit = (int) $before;
125
        }
126
127
        if ($clientLimit) {
128
            $limit = (int) $this->getParameterFromContext($context, $this->options['items_per_page_parameter_name'], $limit);
129
            $maxItemsPerPage = $this->options['maximum_items_per_page'];
130
131
            if (null !== $resourceClass) {
132
                $resourceMetadata = $this->resourceMetadataFactory->create($resourceClass);
133
                $maxItemsPerPage = $resourceMetadata->getCollectionOperationAttribute($operationName, 'maximum_items_per_page', $maxItemsPerPage, true);
134
            }
135
136
            if (null !== $maxItemsPerPage && $limit > $maxItemsPerPage) {
137
                $limit = $maxItemsPerPage;
138
            }
139
        }
140
141
        if (0 > $limit) {
142
            throw new InvalidArgumentException('Limit should not be less than 0');
143
        }
144
145
        return $limit;
146
    }
147
148
    /**
149
     * Gets info about the pagination.
150
     *
151
     * Returns an array with the following info as values:
152
     *   - the page {@see Pagination::getPage()}
153
     *   - the offset {@see Pagination::getOffset()}
154
     *   - the limit {@see Pagination::getLimit()}
155
     *
156
     * @throws InvalidArgumentException
157
     */
158
    public function getPagination(string $resourceClass = null, string $operationName = null, array $context = []): array
159
    {
160
        $page = $this->getPage($context);
161
        $limit = $this->getLimit($resourceClass, $operationName, $context);
162
163
        if (0 === $limit && 1 < $page) {
164
            throw new InvalidArgumentException('Page should not be greater than 1 if limit is equal to 0');
165
        }
166
167
        return [$page, $this->getOffset($resourceClass, $operationName, $context), $limit];
168
    }
169
170
    /**
171
     * Is the pagination enabled?
172
     */
173
    public function isEnabled(string $resourceClass = null, string $operationName = null, array $context = []): bool
174
    {
175
        return $this->getEnabled($context, $resourceClass, $operationName);
176
    }
177
178
    /**
179
     * Is the pagination enabled for GraphQL?
180
     */
181
    public function isGraphQlEnabled(?string $resourceClass = null, ?string $operationName = null, array $context = []): bool
0 ignored issues
show
Unused Code introduced by
The parameter $context is not used and could be removed. ( Ignorable by Annotation )

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

181
    public function isGraphQlEnabled(?string $resourceClass = null, ?string $operationName = null, /** @scrutinizer ignore-unused */ array $context = []): bool

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
182
    {
183
        return $this->getGraphQlEnabled($resourceClass, $operationName);
184
    }
185
186
    /**
187
     * Is the partial pagination enabled?
188
     */
189
    public function isPartialEnabled(string $resourceClass = null, string $operationName = null, array $context = []): bool
190
    {
191
        return $this->getEnabled($context, $resourceClass, $operationName, true);
192
    }
193
194
    /**
195
     * Is the classic or partial pagination enabled?
196
     */
197
    private function getEnabled(array $context, string $resourceClass = null, string $operationName = null, bool $partial = false): bool
198
    {
199
        $enabled = $this->options[$partial ? 'partial' : 'enabled'];
200
        $clientEnabled = $this->options[$partial ? 'client_partial' : 'client_enabled'];
201
202
        if (null !== $resourceClass) {
203
            $resourceMetadata = $this->resourceMetadataFactory->create($resourceClass);
204
            $enabled = $resourceMetadata->getCollectionOperationAttribute($operationName, $partial ? 'pagination_partial' : 'pagination_enabled', $enabled, true);
205
206
            $clientEnabled = $resourceMetadata->getCollectionOperationAttribute($operationName, $partial ? 'pagination_client_partial' : 'pagination_client_enabled', $clientEnabled, true);
207
        }
208
209
        if ($clientEnabled) {
210
            return filter_var($this->getParameterFromContext($context, $this->options[$partial ? 'partial_parameter_name' : 'enabled_parameter_name'], $enabled), FILTER_VALIDATE_BOOLEAN);
211
        }
212
213
        return (bool) $enabled;
214
    }
215
216
    private function getGraphQlEnabled(?string $resourceClass, ?string $operationName): bool
217
    {
218
        $enabled = $this->graphQlOptions['enabled'];
219
220
        if (null !== $resourceClass) {
221
            $resourceMetadata = $this->resourceMetadataFactory->create($resourceClass);
222
223
            return (bool) $resourceMetadata->getGraphqlAttribute($operationName, 'pagination_enabled', $enabled, true);
0 ignored issues
show
Bug introduced by
It seems like $operationName can also be of type null; however, parameter $operationName of ApiPlatform\Core\Metadat...::getGraphqlAttribute() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

223
            return (bool) $resourceMetadata->getGraphqlAttribute(/** @scrutinizer ignore-type */ $operationName, 'pagination_enabled', $enabled, true);
Loading history...
224
        }
225
226
        return $enabled;
227
    }
228
229
    /**
230
     * Gets the given pagination parameter name from the given context.
231
     *
232
     * @param mixed|null $default
233
     */
234
    private function getParameterFromContext(array $context, string $parameterName, $default = null)
235
    {
236
        $filters = $context['filters'] ?? [];
237
238
        return \array_key_exists($parameterName, $filters) ? $filters[$parameterName] : $default;
239
    }
240
}
241