Passed
Push — master ( 81d276...59fa1e )
by Kévin
04:12 queued 10s
created

Pagination::getGraphQlEnabled()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 15
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

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

186
    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...
187
    {
188
        return $this->getGraphQlEnabled($resourceClass, $operationName);
189
    }
190
191
    /**
192
     * Is the partial pagination enabled?
193
     */
194
    public function isPartialEnabled(string $resourceClass = null, string $operationName = null, array $context = []): bool
195
    {
196
        return $this->getEnabled($context, $resourceClass, $operationName, true);
197
    }
198
199
    /**
200
     * Is the classic or partial pagination enabled?
201
     */
202
    private function getEnabled(array $context, string $resourceClass = null, string $operationName = null, bool $partial = false): bool
203
    {
204
        $enabled = $this->options[$partial ? 'partial' : 'enabled'];
205
        $clientEnabled = $this->options[$partial ? 'client_partial' : 'client_enabled'];
206
207
        if (null !== $resourceClass) {
208
            $resourceMetadata = $this->resourceMetadataFactory->create($resourceClass);
209
            $enabled = $resourceMetadata->getCollectionOperationAttribute($operationName, $partial ? 'pagination_partial' : 'pagination_enabled', $enabled, true);
210
211
            $clientEnabled = $resourceMetadata->getCollectionOperationAttribute($operationName, $partial ? 'pagination_client_partial' : 'pagination_client_enabled', $clientEnabled, true);
212
        }
213
214
        if ($clientEnabled) {
215
            return filter_var($this->getParameterFromContext($context, $this->options[$partial ? 'partial_parameter_name' : 'enabled_parameter_name'], $enabled), FILTER_VALIDATE_BOOLEAN);
216
        }
217
218
        return (bool) $enabled;
219
    }
220
221
    private function getGraphQlEnabled(?string $resourceClass, ?string $operationName): bool
222
    {
223
        $enabled = $this->graphQlOptions['enabled'];
224
225
        if (null !== $resourceClass) {
226
            try {
227
                $resourceMetadata = $this->resourceMetadataFactory->create($resourceClass);
228
            } catch (ResourceClassNotFoundException $e) {
229
                return $enabled;
230
            }
231
232
            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

232
            return (bool) $resourceMetadata->getGraphqlAttribute(/** @scrutinizer ignore-type */ $operationName, 'pagination_enabled', $enabled, true);
Loading history...
233
        }
234
235
        return $enabled;
236
    }
237
238
    /**
239
     * Gets the given pagination parameter name from the given context.
240
     *
241
     * @param mixed|null $default
242
     */
243
    private function getParameterFromContext(array $context, string $parameterName, $default = null)
244
    {
245
        $filters = $context['filters'] ?? [];
246
247
        return \array_key_exists($parameterName, $filters) ? $filters[$parameterName] : $default;
248
    }
249
}
250