Passed
Pull Request — master (#2142)
by Alan
03:16
created

Pagination::getPage()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 13
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 7
dl 0
loc 13
rs 10
c 0
b 0
f 0
cc 2
nc 2
nop 1
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 $resourceMetadataFactory;
28
29
    public function __construct(ResourceMetadataFactoryInterface $resourceMetadataFactory, array $options = [])
30
    {
31
        $this->resourceMetadataFactory = $resourceMetadataFactory;
32
        $this->options = array_merge([
33
            'enabled' => true,
34
            'client_enabled' => false,
35
            'client_items_per_page' => false,
36
            'items_per_page' => 30,
37
            'page_default' => 1,
38
            'page_parameter_name' => 'page',
39
            'enabled_parameter_name' => 'pagination',
40
            'items_per_page_parameter_name' => 'itemsPerPage',
41
            'maximum_items_per_page' => null,
42
            'partial' => false,
43
            'client_partial' => false,
44
            'partial_parameter_name' => 'partial',
45
        ], $options);
46
    }
47
48
    /**
49
     * Gets the current page.
50
     *
51
     * @throws InvalidArgumentException
52
     */
53
    public function getPage(array $context = []): int
54
    {
55
        $page = (int) $this->getParameterFromContext(
56
            $context,
57
            $this->options['page_parameter_name'],
58
            $this->options['page_default']
59
        );
60
61
        if (1 > $page) {
62
            throw new InvalidArgumentException('Page should not be less than 1');
63
        }
64
65
        return $page;
66
    }
67
68
    /**
69
     * Gets the current offset.
70
     */
71
    public function getOffset(string $resourceClass = null, string $operationName = null, array $context = []): int
72
    {
73
        $graphql = $context['graphql'] ?? false;
74
75
        $limit = $this->getLimit($resourceClass, $operationName, $context);
76
77
        if ($graphql && null !== ($after = $this->getParameterFromContext($context, 'after'))) {
78
            return false === ($after = base64_decode($after, true)) ? 0 : (int) $after + 1; // break for UUID?
0 ignored issues
show
Unused Code Comprehensibility introduced by
43% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
79
        }
80
81
        if ($graphql && null !== ($before = $this->getParameterFromContext($context, 'before'))) {
82
            return ($offset = (false === ($before = base64_decode($before, true)) ? 0 : (int) $before - $limit)) < 0 ? 0 : $offset;  // break for UUID?
0 ignored issues
show
Unused Code Comprehensibility introduced by
43% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
83
        }
84
85
        if ($graphql && null !== ($last = $this->getParameterFromContext($context, 'last'))) {
86
            return ($offset = ($context['count'] ?? 0) - $last) < 0 ? 0 : $offset;
87
        }
88
89
        return ($this->getPage($context) - 1) * $limit;
90
    }
91
92
    /**
93
     * Gets the current limit.
94
     *
95
     * @throws InvalidArgumentException
96
     */
97
    public function getLimit(string $resourceClass = null, string $operationName = null, array $context = []): int
98
    {
99
        $graphql = $context['graphql'] ?? false;
100
101
        $limit = $this->options['items_per_page'];
102
        $clientLimit = $this->options['client_items_per_page'];
103
104
        if (null !== $resourceClass) {
105
            $resourceMetadata = $this->resourceMetadataFactory->create($resourceClass);
106
            $limit = $resourceMetadata->getCollectionOperationAttribute($operationName, 'pagination_items_per_page', $limit, true);
107
            $clientLimit = $resourceMetadata->getCollectionOperationAttribute($operationName, 'pagination_client_items_per_page', $clientLimit, true);
108
        }
109
110
        if ($graphql && null !== ($first = $this->getParameterFromContext($context, 'first'))) {
111
            $limit = $first;
112
        }
113
114
        if ($graphql && null !== ($last = $this->getParameterFromContext($context, 'last'))) {
115
            $limit = $last;
116
        }
117
118
        if ($graphql && null !== ($before = $this->getParameterFromContext($context, 'before'))
119
            && (false === ($before = base64_decode($before, true)) ? 0 : (int) $before - $limit) < 0) {
120
            $limit = (int) $before;
121
        }
122
123
        if ($clientLimit) {
124
            $limit = (int) $this->getParameterFromContext($context, $this->options['items_per_page_parameter_name'], $limit);
125
            $maxItemsPerPage = $this->options['maximum_items_per_page'];
126
127
            if (null !== $resourceClass) {
128
                $resourceMetadata = $this->resourceMetadataFactory->create($resourceClass);
129
                $maxItemsPerPage = $resourceMetadata->getCollectionOperationAttribute($operationName, 'maximum_items_per_page', $maxItemsPerPage, true);
130
            }
131
132
            if (null !== $maxItemsPerPage && $limit > $maxItemsPerPage) {
133
                $limit = $maxItemsPerPage;
134
            }
135
        }
136
137
        if (0 > $limit) {
138
            throw new InvalidArgumentException('Limit should not be less than 0');
139
        }
140
141
        return $limit;
142
    }
143
144
    /**
145
     * Gets info about the pagination.
146
     *
147
     * Returns an array with the following info as values:
148
     *   - the page {@see Pagination::getPage()}
149
     *   - the offset {@see Pagination::getOffset()}
150
     *   - the limit {@see Pagination::getLimit()}
151
     *
152
     * @throws InvalidArgumentException
153
     */
154
    public function getPagination(string $resourceClass = null, string $operationName = null, array $context = []): array
155
    {
156
        $page = $this->getPage($context);
157
        $limit = $this->getLimit($resourceClass, $operationName, $context);
158
159
        if (0 === $limit && 1 < $page) {
160
            throw new InvalidArgumentException('Page should not be greater than 1 if limit is equal to 0');
161
        }
162
163
        return [$page, $this->getOffset($resourceClass, $operationName, $context), $limit];
164
    }
165
166
    /**
167
     * Is the pagination enabled?
168
     */
169
    public function isEnabled(string $resourceClass = null, string $operationName = null, array $context = []): bool
170
    {
171
        return $this->getEnabled($context, $resourceClass, $operationName);
172
    }
173
174
    /**
175
     * Is the partial pagination enabled?
176
     */
177
    public function isPartialEnabled(string $resourceClass = null, string $operationName = null, array $context = []): bool
178
    {
179
        return $this->getEnabled($context, $resourceClass, $operationName, true);
180
    }
181
182
    /**
183
     * Is the classic or partial pagination enabled?
184
     */
185
    private function getEnabled(array $context, string $resourceClass = null, string $operationName = null, bool $partial = false): bool
186
    {
187
        $enabled = $this->options[$partial ? 'partial' : 'enabled'];
188
        $clientEnabled = $this->options[$partial ? 'client_partial' : 'client_enabled'];
189
190
        if (null !== $resourceClass) {
191
            $resourceMetadata = $this->resourceMetadataFactory->create($resourceClass);
192
            $enabled = $resourceMetadata->getCollectionOperationAttribute($operationName, $partial ? 'pagination_partial' : 'pagination_enabled', $enabled, true);
193
194
            $clientEnabled = $resourceMetadata->getCollectionOperationAttribute($operationName, $partial ? 'pagination_client_partial' : 'pagination_client_enabled', $clientEnabled, true);
195
        }
196
197
        if ($clientEnabled) {
198
            return filter_var($this->getParameterFromContext($context, $this->options[$partial ? 'partial_parameter_name' : 'enabled_parameter_name'], $enabled), FILTER_VALIDATE_BOOLEAN);
199
        }
200
201
        return $enabled;
202
    }
203
204
    /**
205
     * Gets the given pagination parameter name from the given context.
206
     */
207
    private function getParameterFromContext(array $context, string $parameterName, $default = null)
208
    {
209
        $filters = $context['filters'] ?? [];
210
211
        return \array_key_exists($parameterName, $filters) ? $filters[$parameterName] : $default;
212
    }
213
}
214