20
20
use ApiPlatform \Core \DataProvider \Pagination ;
21
21
use ApiPlatform \Core \Exception \InvalidArgumentException ;
22
22
use ApiPlatform \Core \Metadata \Resource \Factory \ResourceMetadataFactoryInterface ;
23
+ use ApiPlatform \Core \Metadata \Resource \ResourceMetadata ;
23
24
use Doctrine \Common \Persistence \ManagerRegistry ;
24
25
use Doctrine \ORM \QueryBuilder ;
25
26
use Doctrine \ORM \Tools \Pagination \Paginator as DoctrineOrmPaginator ;
27
+ use Symfony \Component \HttpFoundation \Request ;
26
28
use Symfony \Component \HttpFoundation \RequestStack ;
27
29
28
30
/**
34
36
final class PaginationExtension implements ContextAwareQueryResultCollectionExtensionInterface
35
37
{
36
38
private $ managerRegistry ;
39
+ private $ requestStack ;
37
40
private $ resourceMetadataFactory ;
41
+ private $ enabled ;
42
+ private $ clientEnabled ;
43
+ private $ clientItemsPerPage ;
44
+ private $ itemsPerPage ;
45
+ private $ pageParameterName ;
46
+ private $ enabledParameterName ;
47
+ private $ itemsPerPageParameterName ;
48
+ private $ maximumItemPerPage ;
49
+ private $ partial ;
50
+ private $ clientPartial ;
51
+ private $ partialParameterName ;
38
52
private $ pagination ;
39
53
40
54
/**
@@ -47,45 +61,40 @@ public function __construct(ManagerRegistry $managerRegistry, /* ResourceMetadat
47
61
@trigger_error (sprintf ('Passing an instance of "%s" as second argument of "%s" is deprecated since API Platform 2.4 and will not be possible anymore in API Platform 3. Pass an instance of "%s" instead. ' , RequestStack::class, self ::class, ResourceMetadataFactoryInterface::class), E_USER_DEPRECATED );
48
62
@trigger_error (sprintf ('Passing an instance of "%s" as third argument of "%s" is deprecated since API Platform 2.4 and will not be possible anymore in API Platform 3. Pass an instance of "%s" instead. ' , ResourceMetadataFactoryInterface::class, self ::class, Pagination::class), E_USER_DEPRECATED );
49
63
50
- $ requestStack = $ resourceMetadataFactory ;
64
+ $ this -> requestStack = $ resourceMetadataFactory ;
51
65
$ resourceMetadataFactory = $ pagination ;
66
+ $ pagination = null ;
52
67
68
+ $ args = \array_slice (\func_get_args (), 3 );
53
69
$ legacyPaginationArgs = [
54
- 3 => ['arg_name ' => ' enabled ' , ' option_name ' => 'enabled ' , 'type ' => 'bool ' , 'default ' => true ],
55
- 4 => ['arg_name ' => 'clientEnabled ' , ' option_name ' => ' client_enabled ' , 'type ' => 'bool ' , 'default ' => false ],
56
- 5 => ['arg_name ' => 'clientItemsPerPage ' , ' option_name ' => ' client_items_per_page ' , 'type ' => 'bool ' , 'default ' => false ],
57
- 6 => ['arg_name ' => 'itemsPerPage ' , ' option_name ' => ' items_per_page ' , 'type ' => 'int ' , 'default ' => 30 ],
58
- 7 => ['arg_name ' => 'pageParameterName ' , ' option_name ' => ' page_parameter_name ' , 'type ' => 'string ' , 'default ' => 'page ' ],
59
- 8 => ['arg_name ' => 'enabledParameterName ' , ' option_name ' => ' enabled_parameter_name ' , 'type ' => 'string ' , 'default ' => 'pagination ' ],
60
- 9 => ['arg_name ' => 'itemsPerPageParameterName ' , ' option_name ' => ' items_per_page_parameter_name ' , 'type ' => 'string ' , 'default ' => 'itemsPerPage ' ],
61
- 10 => ['arg_name ' => 'maximumItemPerPage ' , ' option_name ' => ' maximum_items_per_page ' , 'type ' => 'int ' , 'default ' => null ],
62
- 11 => ['arg_name ' => ' partial ' , ' option_name ' => 'partial ' , 'type ' => 'bool ' , 'default ' => false ],
63
- 12 => ['arg_name ' => 'clientPartial ' , ' option_name ' => ' client_partial ' , 'type ' => 'bool ' , 'default ' => false ],
64
- 13 => ['arg_name ' => 'partialParameterName ' , ' option_name ' => ' partial_parameter_name ' , 'type ' => 'string ' , 'default ' => 'partial ' ],
70
+ ['arg_name ' => 'enabled ' , 'type ' => 'bool ' , 'default ' => true ],
71
+ ['arg_name ' => 'clientEnabled ' , 'type ' => 'bool ' , 'default ' => false ],
72
+ ['arg_name ' => 'clientItemsPerPage ' , 'type ' => 'bool ' , 'default ' => false ],
73
+ ['arg_name ' => 'itemsPerPage ' , 'type ' => 'int ' , 'default ' => 30 ],
74
+ ['arg_name ' => 'pageParameterName ' , 'type ' => 'string ' , 'default ' => 'page ' ],
75
+ ['arg_name ' => 'enabledParameterName ' , 'type ' => 'string ' , 'default ' => 'pagination ' ],
76
+ ['arg_name ' => 'itemsPerPageParameterName ' , 'type ' => 'string ' , 'default ' => 'itemsPerPage ' ],
77
+ ['arg_name ' => 'maximumItemPerPage ' , 'type ' => 'int ' , 'default ' => null ],
78
+ ['arg_name ' => 'partial ' , 'type ' => 'bool ' , 'default ' => false ],
79
+ ['arg_name ' => 'clientPartial ' , 'type ' => 'bool ' , 'default ' => false ],
80
+ ['arg_name ' => 'partialParameterName ' , 'type ' => 'string ' , 'default ' => 'partial ' ],
65
81
];
66
82
67
- $ paginationOptions = array_column ($ legacyPaginationArgs , 'default ' , 'option_name ' );
83
+ foreach ($ legacyPaginationArgs as $ pos => $ arg ) {
84
+ if (array_key_exists ($ pos , $ args )) {
85
+ @trigger_error (sprintf ('Passing "$%s" arguments is deprecated since API Platform 2.4 and will not be possible anymore in API Platform 3. Pass an instance of "%s" as third argument instead. ' , implode ('", "$ ' , array_column ($ legacyPaginationArgs , 'arg_name ' )), Paginator::class), E_USER_DEPRECATED );
68
86
69
- if (0 < \count ($ legacyArgs = \array_slice (\func_get_args (), 3 , null , true ))) {
70
- @trigger_error (sprintf ('Passing "$%s" arguments is deprecated since API Platform 2.4 and will not be possible anymore in API Platform 3. Pass an instance of "%s" as third argument instead. ' , implode ('", "$ ' , array_column ($ legacyPaginationArgs , 'arg_name ' )), Paginator::class), E_USER_DEPRECATED );
71
-
72
- foreach ($ legacyArgs as $ pos => $ arg ) {
73
- [
74
- 'arg_name ' => $ argName ,
75
- 'option_name ' => $ optionName ,
76
- 'type ' => $ type ,
77
- 'default ' => $ default ,
78
- ] = $ legacyPaginationArgs [$ pos ];
79
-
80
- if (!((null === $ default && null === $ arg ) || \call_user_func ("is_ {$ type }" , $ arg ))) {
81
- throw new InvalidArgumentException (sprintf ('The "$%s" argument is expected to be a %s%s. ' , $ argName , $ type , null === $ default ? ' or null ' : '' ));
87
+ if (!((null === $ arg ['default ' ] && null === $ args [$ pos ]) || \call_user_func ("is_ {$ arg ['type ' ]}" , $ args [$ pos ]))) {
88
+ throw new InvalidArgumentException (sprintf ('The "$%s" argument is expected to be a %s%s. ' , $ arg ['arg_name ' ], $ arg ['type ' ], null === $ arg ['default ' ] ? ' or null ' : '' ));
82
89
}
83
90
84
- $ paginationOptions [$ optionName ] = $ arg ;
91
+ $ value = $ args [$ pos ];
92
+ } else {
93
+ $ value = $ arg ['default ' ];
85
94
}
86
- }
87
95
88
- $ pagination = new Pagination ($ requestStack , $ resourceMetadataFactory , $ paginationOptions );
96
+ $ this ->{$ arg ['arg_name ' ]} = $ value ;
97
+ }
89
98
} elseif (!$ resourceMetadataFactory instanceof ResourceMetadataFactoryInterface) {
90
99
throw new InvalidArgumentException (sprintf ('The "$resourceMetadataFactory" argument is expected to be an implementation of the "%s" interface. ' , ResourceMetadataFactoryInterface::class));
91
100
} elseif (!$ pagination instanceof Pagination) {
@@ -100,17 +109,13 @@ public function __construct(ManagerRegistry $managerRegistry, /* ResourceMetadat
100
109
/**
101
110
* {@inheritdoc}
102
111
*/
103
- public function applyToCollection (QueryBuilder $ queryBuilder , QueryNameGeneratorInterface $ queryNameGenerator , string $ resourceClass = null , string $ operationName = null , array $ context = [])
112
+ public function applyToCollection (QueryBuilder $ queryBuilder , QueryNameGeneratorInterface $ queryNameGenerator , string $ resourceClass , string $ operationName = null , array $ context = [])
104
113
{
105
- if (null === $ resourceClass ) {
106
- throw new InvalidArgumentException ('The "$resourceClass" parameter must not be null ' );
107
- }
108
-
109
- if (!$ this ->pagination ->isEnabled ($ resourceClass , $ operationName )) {
114
+ if (null === $ pagination = $ this ->getPagination ($ resourceClass , $ operationName , $ context )) {
110
115
return ;
111
116
}
112
117
113
- [, $ offset , $ limit ] = $ this -> pagination -> getPagination ( $ resourceClass , $ operationName ) ;
118
+ [$ offset , $ limit ] = $ pagination ;
114
119
115
120
$ queryBuilder
116
121
->setFirstResult ($ offset )
@@ -122,7 +127,15 @@ public function applyToCollection(QueryBuilder $queryBuilder, QueryNameGenerator
122
127
*/
123
128
public function supportsResult (string $ resourceClass , string $ operationName = null , array $ context = []): bool
124
129
{
125
- return $ this ->pagination ->isEnabled ($ resourceClass , $ operationName );
130
+ if (null === $ this ->requestStack ) {
131
+ return $ this ->pagination ->isEnabled ($ resourceClass , $ operationName , $ context );
132
+ }
133
+
134
+ if (null === $ request = $ this ->requestStack ->getCurrentRequest ()) {
135
+ return false ;
136
+ }
137
+
138
+ return $ this ->isPaginationEnabled ($ request , $ this ->resourceMetadataFactory ->create ($ resourceClass ), $ operationName );
126
139
}
127
140
128
141
/**
@@ -133,14 +146,127 @@ public function getResult(QueryBuilder $queryBuilder, string $resourceClass = nu
133
146
$ doctrineOrmPaginator = new DoctrineOrmPaginator ($ queryBuilder , $ this ->useFetchJoinCollection ($ queryBuilder , $ resourceClass , $ operationName ));
134
147
$ doctrineOrmPaginator ->setUseOutputWalkers ($ this ->useOutputWalkers ($ queryBuilder ));
135
148
136
- if ($ this ->pagination ->isPartialEnabled ($ resourceClass , $ operationName )) {
149
+ if (null === $ this ->requestStack ) {
150
+ $ isPartialEnabled = $ this ->pagination ->isPartialEnabled ($ resourceClass , $ operationName , $ context );
151
+ } else {
152
+ $ isPartialEnabled = $ this ->isPartialPaginationEnabled (
153
+ $ this ->requestStack ->getCurrentRequest (),
154
+ null === $ resourceClass ? null : $ this ->resourceMetadataFactory ->create ($ resourceClass ),
155
+ $ operationName
156
+ );
157
+ }
158
+
159
+ if ($ isPartialEnabled ) {
137
160
return new class ($ doctrineOrmPaginator ) extends AbstractPaginator {
138
161
};
139
162
}
140
163
141
164
return new Paginator ($ doctrineOrmPaginator );
142
165
}
143
166
167
+ /**
168
+ * @throws InvalidArgumentException
169
+ */
170
+ private function getPagination (string $ resourceClass , ?string $ operationName , array $ context ): ?array
171
+ {
172
+ $ request = null ;
173
+ if (null !== $ this ->requestStack && null === $ request = $ this ->requestStack ->getCurrentRequest ()) {
174
+ return null ;
175
+ }
176
+
177
+ if (null === $ request ) {
178
+ if (!$ this ->pagination ->isEnabled ($ resourceClass , $ operationName , $ context )) {
179
+ return null ;
180
+ }
181
+
182
+ return \array_slice ($ this ->pagination ->getPagination ($ resourceClass , $ operationName , $ context ), 1 );
183
+ }
184
+
185
+ $ resourceMetadata = $ this ->resourceMetadataFactory ->create ($ resourceClass );
186
+ if (!$ this ->isPaginationEnabled ($ request , $ resourceMetadata , $ operationName )) {
187
+ return null ;
188
+ }
189
+
190
+ $ itemsPerPage = $ resourceMetadata ->getCollectionOperationAttribute ($ operationName , 'pagination_items_per_page ' , $ this ->itemsPerPage , true );
191
+ if ($ request ->attributes ->get ('_graphql ' )) {
192
+ $ collectionArgs = $ request ->attributes ->get ('_graphql_collections_args ' , []);
193
+ $ itemsPerPage = $ collectionArgs [$ resourceClass ]['first ' ] ?? $ itemsPerPage ;
194
+ }
195
+
196
+ if ($ resourceMetadata ->getCollectionOperationAttribute ($ operationName , 'pagination_client_items_per_page ' , $ this ->clientItemsPerPage , true )) {
197
+ $ maxItemsPerPage = $ resourceMetadata ->getCollectionOperationAttribute ($ operationName , 'maximum_items_per_page ' , $ this ->maximumItemPerPage , true );
198
+ $ itemsPerPage = (int ) $ this ->getPaginationParameter ($ request , $ this ->itemsPerPageParameterName , $ itemsPerPage );
199
+ $ itemsPerPage = (null !== $ maxItemsPerPage && $ itemsPerPage >= $ maxItemsPerPage ? $ maxItemsPerPage : $ itemsPerPage );
200
+ }
201
+
202
+ if (0 > $ itemsPerPage ) {
203
+ throw new InvalidArgumentException ('Item per page parameter should not be less than 0 ' );
204
+ }
205
+
206
+ $ page = (int ) $ this ->getPaginationParameter ($ request , $ this ->pageParameterName , 1 );
207
+
208
+ if (1 > $ page ) {
209
+ throw new InvalidArgumentException ('Page should not be less than 1 ' );
210
+ }
211
+
212
+ if (0 === $ itemsPerPage && 1 < $ page ) {
213
+ throw new InvalidArgumentException ('Page should not be greater than 1 if itemsPerPage is equal to 0 ' );
214
+ }
215
+
216
+ $ firstResult = ($ page - 1 ) * $ itemsPerPage ;
217
+ if ($ request ->attributes ->get ('_graphql ' )) {
218
+ $ collectionArgs = $ request ->attributes ->get ('_graphql_collections_args ' , []);
219
+ if (isset ($ collectionArgs [$ resourceClass ]['after ' ])) {
220
+ $ after = base64_decode ($ collectionArgs [$ resourceClass ]['after ' ], true );
221
+ $ firstResult = (int ) $ after ;
222
+ $ firstResult = false === $ after ? $ firstResult : ++$ firstResult ;
223
+ }
224
+ }
225
+
226
+ return [$ firstResult , $ itemsPerPage ];
227
+ }
228
+
229
+ private function isPartialPaginationEnabled (Request $ request = null , ResourceMetadata $ resourceMetadata = null , string $ operationName = null ): bool
230
+ {
231
+ $ enabled = $ this ->partial ;
232
+ $ clientEnabled = $ this ->clientPartial ;
233
+
234
+ if ($ resourceMetadata ) {
235
+ $ enabled = $ resourceMetadata ->getCollectionOperationAttribute ($ operationName , 'pagination_partial ' , $ enabled , true );
236
+
237
+ if ($ request ) {
238
+ $ clientEnabled = $ resourceMetadata ->getCollectionOperationAttribute ($ operationName , 'pagination_client_partial ' , $ clientEnabled , true );
239
+ }
240
+ }
241
+
242
+ if ($ clientEnabled && $ request ) {
243
+ $ enabled = filter_var ($ this ->getPaginationParameter ($ request , $ this ->partialParameterName , $ enabled ), FILTER_VALIDATE_BOOLEAN );
244
+ }
245
+
246
+ return $ enabled ;
247
+ }
248
+
249
+ private function isPaginationEnabled (Request $ request , ResourceMetadata $ resourceMetadata , string $ operationName = null ): bool
250
+ {
251
+ $ enabled = $ resourceMetadata ->getCollectionOperationAttribute ($ operationName , 'pagination_enabled ' , $ this ->enabled , true );
252
+ $ clientEnabled = $ resourceMetadata ->getCollectionOperationAttribute ($ operationName , 'pagination_client_enabled ' , $ this ->clientEnabled , true );
253
+
254
+ if ($ clientEnabled ) {
255
+ $ enabled = filter_var ($ this ->getPaginationParameter ($ request , $ this ->enabledParameterName , $ enabled ), FILTER_VALIDATE_BOOLEAN );
256
+ }
257
+
258
+ return $ enabled ;
259
+ }
260
+
261
+ private function getPaginationParameter (Request $ request , string $ parameterName , $ default = null )
262
+ {
263
+ if (null !== $ paginationAttribute = $ request ->attributes ->get ('_api_pagination ' )) {
264
+ return array_key_exists ($ parameterName , $ paginationAttribute ) ? $ paginationAttribute [$ parameterName ] : $ default ;
265
+ }
266
+
267
+ return $ request ->query ->get ($ parameterName , $ default );
268
+ }
269
+
144
270
/**
145
271
* Determines whether the Paginator should fetch join collections, if the root entity uses composite identifiers it should not.
146
272
*
0 commit comments