Passed
Pull Request — 2.4 (#2710)
by GRASSIOT
03:07
created

Pagination::isPartialEnabled()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 1
dl 0
loc 3
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 3
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;
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;
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
     * @param mixed|null $default
208
     */
209
    private function getParameterFromContext(array $context, string $parameterName, $default = null)
210
    {
211
        $filters = $context['filters'] ?? [];
212
213
        return \array_key_exists($parameterName, $filters) ? $filters[$parameterName] : $default;
214
    }
215
}
216