This project does not seem to handle request data directly as such no vulnerable execution paths were found.
include
, or for example
via PHP's auto-loading mechanism.
These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more
1 | <?php declare (strict_types = 1); |
||
2 | |||
3 | namespace Limoncello\Flute\Validation\JsonApi; |
||
4 | |||
5 | /** |
||
6 | * Copyright 2015-2019 [email protected] |
||
7 | * |
||
8 | * Licensed under the Apache License, Version 2.0 (the "License"); |
||
9 | * you may not use this file except in compliance with the License. |
||
10 | * You may obtain a copy of the License at |
||
11 | * |
||
12 | * http://www.apache.org/licenses/LICENSE-2.0 |
||
13 | * |
||
14 | * Unless required by applicable law or agreed to in writing, software |
||
15 | * distributed under the License is distributed on an "AS IS" BASIS, |
||
16 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||
17 | * See the License for the specific language governing permissions and |
||
18 | * limitations under the License. |
||
19 | */ |
||
20 | |||
21 | use Generator; |
||
22 | use Limoncello\Common\Reflection\ClassIsTrait; |
||
23 | use Limoncello\Contracts\L10n\FormatterFactoryInterface; |
||
24 | use Limoncello\Contracts\L10n\FormatterInterface; |
||
25 | use Limoncello\Flute\Contracts\Validation\ErrorCodes; |
||
26 | use Limoncello\Flute\Contracts\Validation\JsonApiQueryParserInterface; |
||
27 | use Limoncello\Flute\Contracts\Validation\JsonApiQueryRulesInterface; |
||
28 | use Limoncello\Flute\Contracts\Validation\JsonApiQueryRulesSerializerInterface; |
||
29 | use Limoncello\Flute\Exceptions\InvalidQueryParametersException; |
||
30 | use Limoncello\Flute\L10n\Messages; |
||
31 | use Limoncello\Flute\Validation\JsonApi\Execution\JsonApiErrorCollection; |
||
32 | use Limoncello\Validation\Contracts\Captures\CaptureAggregatorInterface; |
||
33 | use Limoncello\Validation\Contracts\Errors\ErrorAggregatorInterface; |
||
34 | use Limoncello\Validation\Contracts\Execution\ContextStorageInterface; |
||
35 | use Limoncello\Validation\Errors\Error as ValidationError; |
||
36 | use Limoncello\Validation\Execution\BlockInterpreter; |
||
37 | use Neomerx\JsonApi\Contracts\Schema\ErrorInterface; |
||
38 | use Neomerx\JsonApi\Exceptions\JsonApiException; |
||
39 | use Neomerx\JsonApi\Http\Query\BaseQueryParserTrait; |
||
40 | use Neomerx\JsonApi\Schema\Error as JsonApiError; |
||
41 | use function array_key_exists; |
||
42 | use function assert; |
||
43 | use function count; |
||
44 | use function is_array; |
||
45 | use function is_int; |
||
46 | use function is_string; |
||
47 | use function key; |
||
48 | use function reset; |
||
49 | use function strtolower; |
||
50 | use function trim; |
||
51 | |||
52 | /** |
||
53 | * @package Limoncello\Flute |
||
54 | * |
||
55 | * @SuppressWarnings(PHPMD.TooManyFields) |
||
56 | * @SuppressWarnings(PHPMD.TooManyMethods) |
||
57 | * @SuppressWarnings(PHPMD.TooManyPublicMethods) |
||
58 | * @SuppressWarnings(PHPMD.CouplingBetweenObjects) |
||
59 | * @SuppressWarnings(PHPMD.ExcessiveClassComplexity) |
||
60 | * @SuppressWarnings(PHPMD.ExcessiveClassLength) |
||
61 | */ |
||
62 | class QueryParser implements JsonApiQueryParserInterface |
||
63 | { |
||
64 | use BaseQueryParserTrait { |
||
65 | BaseQueryParserTrait::getFields as getFieldsImpl; |
||
66 | BaseQueryParserTrait::getIncludes as getIncludesImpl; |
||
67 | BaseQueryParserTrait::getSorts as getSortsImpl; |
||
68 | } |
||
69 | use ClassIsTrait; |
||
70 | |||
71 | /** Message */ |
||
72 | public const MSG_ERR_INVALID_PARAMETER = 'Invalid Parameter.'; |
||
73 | |||
74 | /** |
||
75 | * @var array |
||
76 | */ |
||
77 | private $parameters; |
||
78 | |||
79 | /** |
||
80 | * @var string[]|null |
||
81 | */ |
||
82 | private $messages; |
||
83 | |||
84 | /** |
||
85 | * @var null|string |
||
86 | */ |
||
87 | private $identityParameter; |
||
88 | |||
89 | /** |
||
90 | * @var array |
||
91 | */ |
||
92 | private $filterParameters; |
||
93 | |||
94 | /** |
||
95 | * @var bool |
||
96 | */ |
||
97 | private $areFiltersWithAnd; |
||
98 | |||
99 | /** |
||
100 | * @var int|null |
||
101 | */ |
||
102 | private $pagingOffset; |
||
103 | |||
104 | /** |
||
105 | * @var int|null |
||
106 | */ |
||
107 | private $pagingLimit; |
||
108 | |||
109 | /** |
||
110 | * NOTE: Despite the type it is just a string so only static methods can be called from the interface. |
||
111 | * |
||
112 | * @var JsonApiQueryRulesSerializerInterface|string |
||
113 | */ |
||
114 | private $serializerClass; |
||
115 | |||
116 | /** |
||
117 | * @var array |
||
118 | */ |
||
119 | private $serializedRuleSet; |
||
120 | |||
121 | /** |
||
122 | * @var array |
||
123 | */ |
||
124 | private $validationBlocks; |
||
125 | |||
126 | /** |
||
127 | * @var ContextStorageInterface |
||
128 | */ |
||
129 | private $context; |
||
130 | |||
131 | /** |
||
132 | * @var CaptureAggregatorInterface |
||
133 | */ |
||
134 | private $captures; |
||
135 | |||
136 | /** |
||
137 | * @var ErrorAggregatorInterface |
||
138 | */ |
||
139 | private $validationErrors; |
||
140 | |||
141 | /** |
||
142 | * @var JsonApiErrorCollection |
||
143 | */ |
||
144 | private $jsonErrors; |
||
145 | |||
146 | /** |
||
147 | * @var null|mixed |
||
148 | */ |
||
149 | private $cachedIdentity = null; |
||
150 | |||
151 | /** |
||
152 | * @var null|array |
||
153 | */ |
||
154 | private $cachedFilters = null; |
||
155 | |||
156 | /** |
||
157 | * @var null|array |
||
158 | */ |
||
159 | private $cachedFields = null; |
||
160 | |||
161 | /** |
||
162 | * @var null|array |
||
163 | */ |
||
164 | private $cachedSorts = null; |
||
165 | |||
166 | /** |
||
167 | * @var null|array |
||
168 | */ |
||
169 | private $cachedIncludes = null; |
||
170 | |||
171 | /** |
||
172 | * @var FormatterFactoryInterface |
||
173 | */ |
||
174 | private $formatterFactory; |
||
175 | |||
176 | /** |
||
177 | * @var FormatterInterface|null |
||
178 | */ |
||
179 | 38 | private $formatter; |
|
180 | |||
181 | /** |
||
182 | * @param string $rulesClass |
||
183 | * @param string $serializerClass |
||
184 | * @param array $serializedData |
||
185 | * @param ContextStorageInterface $context |
||
186 | * @param CaptureAggregatorInterface $captures |
||
187 | * @param ErrorAggregatorInterface $validationErrors |
||
188 | * @param JsonApiErrorCollection $jsonErrors |
||
189 | * @param FormatterFactoryInterface $formatterFactory |
||
190 | 38 | * @param string[]|null $messages |
|
191 | 38 | */ |
|
192 | 38 | public function __construct( |
|
193 | string $rulesClass, |
||
194 | string $serializerClass, |
||
195 | 38 | array $serializedData, |
|
196 | 38 | ContextStorageInterface $context, |
|
197 | 38 | CaptureAggregatorInterface $captures, |
|
198 | 38 | ErrorAggregatorInterface $validationErrors, |
|
199 | 38 | JsonApiErrorCollection $jsonErrors, |
|
200 | 38 | FormatterFactoryInterface $formatterFactory, |
|
201 | 38 | array $messages = null |
|
202 | 38 | ) { |
|
203 | assert(static::classImplements($rulesClass, JsonApiQueryRulesInterface::class)); |
||
204 | 38 | assert(static::classImplements($serializerClass, JsonApiQueryRulesSerializerInterface::class)); |
|
205 | 38 | ||
206 | 38 | $parameters = []; |
|
207 | $this->setParameters($parameters)->setMessages($messages); |
||
0 ignored issues
–
show
|
|||
208 | 38 | $this->serializerClass = $serializerClass; |
|
209 | $this->context = $context; |
||
210 | $this->captures = $captures; |
||
211 | $this->validationErrors = $validationErrors; |
||
212 | $this->jsonErrors = $jsonErrors; |
||
213 | $this->formatterFactory = $formatterFactory; |
||
214 | 35 | ||
215 | assert($this->serializerClass::hasRules($rulesClass, $serializedData)); |
||
0 ignored issues
–
show
|
|||
216 | 35 | $this->serializedRuleSet = $this->serializerClass::readRules($rulesClass, $serializedData); |
|
0 ignored issues
–
show
|
|||
217 | $this->validationBlocks = $this->serializerClass::readBlocks($serializedData); |
||
0 ignored issues
–
show
|
|||
218 | 35 | ||
219 | $this->clear(); |
||
220 | 35 | } |
|
221 | |||
222 | 31 | /** |
|
223 | * @inheritdoc |
||
224 | */ |
||
225 | public function parse(?string $identity, array $parameters = []): JsonApiQueryParserInterface |
||
226 | { |
||
227 | $this->clear(); |
||
228 | 17 | ||
229 | $this->setIdentityParameter($identity)->setParameters($parameters); |
||
230 | 17 | ||
231 | $this->parsePagingParameters()->parseFilterLink(); |
||
232 | |||
233 | return $this; |
||
234 | } |
||
235 | |||
236 | 1 | /** |
|
237 | * @inheritdoc |
||
238 | 1 | */ |
|
239 | public function areFiltersWithAnd(): bool |
||
240 | { |
||
241 | return $this->areFiltersWithAnd; |
||
242 | } |
||
243 | |||
244 | 14 | /** |
|
245 | * @inheritdoc |
||
246 | 14 | */ |
|
247 | public function hasFilters(): bool |
||
248 | { |
||
249 | return $this->hasParameter(static::PARAM_FILTER); |
||
250 | } |
||
251 | |||
252 | 14 | /** |
|
253 | * @inheritdoc |
||
254 | 14 | */ |
|
255 | public function hasFields(): bool |
||
256 | { |
||
257 | return $this->hasParameter(static::PARAM_FIELDS); |
||
258 | } |
||
259 | |||
260 | 1 | /** |
|
261 | * @inheritdoc |
||
262 | 1 | */ |
|
263 | public function hasIncludes(): bool |
||
264 | { |
||
265 | return $this->hasParameter(static::PARAM_INCLUDE); |
||
266 | } |
||
267 | |||
268 | 1 | /** |
|
269 | * @inheritdoc |
||
270 | 1 | */ |
|
271 | public function hasSorts(): bool |
||
272 | { |
||
273 | return $this->hasParameter(static::PARAM_SORT); |
||
274 | } |
||
275 | |||
276 | 5 | /** |
|
277 | * @inheritdoc |
||
278 | 5 | */ |
|
279 | 5 | public function hasPaging(): bool |
|
280 | { |
||
281 | return $this->hasParameter(static::PARAM_PAGE); |
||
282 | 5 | } |
|
283 | |||
284 | /** |
||
285 | * @inheritdoc |
||
286 | */ |
||
287 | public function getIdentity() |
||
288 | 22 | { |
|
289 | if ($this->cachedIdentity === null) { |
||
290 | 22 | $this->cachedIdentity = $this->getValidatedIdentity(); |
|
291 | 22 | } |
|
292 | |||
293 | return $this->cachedIdentity; |
||
294 | 18 | } |
|
295 | |||
296 | /** |
||
297 | * @inheritdoc |
||
298 | */ |
||
299 | public function getFilters(): array |
||
300 | 3 | { |
|
301 | if ($this->cachedFilters === null) { |
||
302 | 3 | $this->cachedFilters = $this->iterableToArray($this->getValidatedFilters()); |
|
0 ignored issues
–
show
$this->getValidatedFilters() is of type object<Generator> , but the function expects a object<Limoncello\Flute\...ation\JsonApi\iterable> .
It seems like the type of the argument is not accepted by the function/method which you are calling. In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug. We suggest to add an explicit type cast like in the following example: function acceptsInteger($int) { }
$x = '123'; // string "123"
// Instead of
acceptsInteger($x);
// we recommend to use
acceptsInteger((integer) $x);
![]() |
|||
303 | 3 | } |
|
304 | 3 | ||
305 | return $this->cachedFilters; |
||
306 | } |
||
307 | 3 | ||
308 | /** |
||
309 | * @inheritdoc |
||
310 | */ |
||
311 | public function getFields(): array |
||
312 | { |
||
313 | 16 | if ($this->cachedFields === null) { |
|
314 | $fields = $this->getFieldsImpl($this->getParameters(), $this->getInvalidParamMessage()); |
||
315 | 16 | $this->cachedFields = $this->iterableToArray($this->getValidatedFields($fields)); |
|
0 ignored issues
–
show
$fields is of type object<Generator> , but the function expects a object<Limoncello\Flute\...ation\JsonApi\iterable> .
It seems like the type of the argument is not accepted by the function/method which you are calling. In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug. We suggest to add an explicit type cast like in the following example: function acceptsInteger($int) { }
$x = '123'; // string "123"
// Instead of
acceptsInteger($x);
// we recommend to use
acceptsInteger((integer) $x);
![]() $this->getValidatedFields($fields) is of type object<Generator> , but the function expects a object<Limoncello\Flute\...ation\JsonApi\iterable> .
It seems like the type of the argument is not accepted by the function/method which you are calling. In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug. We suggest to add an explicit type cast like in the following example: function acceptsInteger($int) { }
$x = '123'; // string "123"
// Instead of
acceptsInteger($x);
// we recommend to use
acceptsInteger((integer) $x);
![]() |
|||
316 | 16 | } |
|
317 | 16 | ||
318 | return $this->cachedFields; |
||
319 | } |
||
320 | 16 | ||
321 | /** |
||
322 | * @inheritdoc |
||
323 | */ |
||
324 | public function getSorts(): array |
||
325 | { |
||
326 | 16 | if ($this->cachedSorts === null) { |
|
327 | $sorts = $this->getSortsImpl($this->getParameters(), $this->getInvalidParamMessage()); |
||
328 | 16 | $this->cachedSorts = $this->iterableToArray($this->getValidatedSorts($sorts)); |
|
0 ignored issues
–
show
$sorts is of type object<Generator> , but the function expects a object<Limoncello\Flute\...ation\JsonApi\iterable> .
It seems like the type of the argument is not accepted by the function/method which you are calling. In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug. We suggest to add an explicit type cast like in the following example: function acceptsInteger($int) { }
$x = '123'; // string "123"
// Instead of
acceptsInteger($x);
// we recommend to use
acceptsInteger((integer) $x);
![]() $this->getValidatedSorts($sorts) is of type object<Generator> , but the function expects a object<Limoncello\Flute\...ation\JsonApi\iterable> .
It seems like the type of the argument is not accepted by the function/method which you are calling. In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug. We suggest to add an explicit type cast like in the following example: function acceptsInteger($int) { }
$x = '123'; // string "123"
// Instead of
acceptsInteger($x);
// we recommend to use
acceptsInteger((integer) $x);
![]() |
|||
329 | 16 | } |
|
330 | 16 | ||
331 | return $this->cachedSorts; |
||
332 | } |
||
333 | 16 | ||
334 | /** |
||
335 | * @inheritdoc |
||
336 | */ |
||
337 | public function getIncludes(): iterable |
||
338 | { |
||
339 | 17 | if ($this->cachedIncludes === null) { |
|
340 | $includes = $this->getIncludesImpl($this->getParameters(), $this->getInvalidParamMessage()); |
||
341 | 17 | $this->cachedIncludes = $this->iterableToArray($this->getValidatedIncludes($includes)); |
|
0 ignored issues
–
show
$includes is of type object<Generator> , but the function expects a object<Limoncello\Flute\...ation\JsonApi\iterable> .
It seems like the type of the argument is not accepted by the function/method which you are calling. In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug. We suggest to add an explicit type cast like in the following example: function acceptsInteger($int) { }
$x = '123'; // string "123"
// Instead of
acceptsInteger($x);
// we recommend to use
acceptsInteger((integer) $x);
![]() $this->getValidatedIncludes($includes) is of type object<Generator> , but the function expects a object<Limoncello\Flute\...ation\JsonApi\iterable> .
It seems like the type of the argument is not accepted by the function/method which you are calling. In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug. We suggest to add an explicit type cast like in the following example: function acceptsInteger($int) { }
$x = '123'; // string "123"
// Instead of
acceptsInteger($x);
// we recommend to use
acceptsInteger((integer) $x);
![]() |
|||
342 | } |
||
343 | |||
344 | return $this->cachedIncludes; |
||
345 | } |
||
346 | |||
347 | 17 | /** |
|
348 | * @inheritdoc |
||
349 | 17 | */ |
|
350 | public function getPagingOffset(): ?int |
||
351 | { |
||
352 | return $this->pagingOffset; |
||
353 | } |
||
354 | |||
355 | /** |
||
356 | * @inheritdoc |
||
357 | 38 | */ |
|
358 | public function getPagingLimit(): ?int |
||
359 | 38 | { |
|
360 | return $this->pagingLimit; |
||
361 | 38 | } |
|
362 | |||
363 | /** |
||
364 | * @param array $parameters |
||
365 | * |
||
366 | * @return self |
||
367 | 35 | */ |
|
368 | protected function setParameters(array $parameters): self |
||
369 | 35 | { |
|
370 | $this->parameters = $parameters; |
||
371 | |||
372 | return $this; |
||
373 | } |
||
374 | |||
375 | /** |
||
376 | * @return array |
||
377 | 38 | */ |
|
378 | protected function getParameters(): array |
||
379 | 38 | { |
|
380 | return $this->parameters; |
||
381 | 38 | } |
|
382 | |||
383 | /** |
||
384 | * @param array $messages |
||
385 | * |
||
386 | * @return self |
||
387 | */ |
||
388 | protected function setMessages(?array $messages): self |
||
389 | 25 | { |
|
390 | $this->messages = $messages; |
||
391 | 25 | ||
392 | return $this; |
||
393 | 25 | } |
|
394 | |||
395 | /** |
||
396 | * @param string $message |
||
397 | * |
||
398 | * @return string |
||
399 | 6 | */ |
|
400 | protected function getMessage(string $message): string |
||
401 | 6 | { |
|
402 | $hasTranslation = $this->messages !== null && array_key_exists($message, $this->messages) === false; |
||
403 | |||
404 | return $hasTranslation === true ? $this->messages[$message] : $message; |
||
405 | } |
||
406 | |||
407 | 38 | /** |
|
408 | * @return JsonApiErrorCollection |
||
409 | 38 | */ |
|
410 | protected function getJsonErrors(): JsonApiErrorCollection |
||
411 | { |
||
412 | return $this->jsonErrors; |
||
413 | } |
||
414 | |||
415 | 38 | /** |
|
416 | * @return ErrorAggregatorInterface |
||
417 | 38 | */ |
|
418 | protected function getValidationErrors(): ErrorAggregatorInterface |
||
419 | { |
||
420 | return $this->validationErrors; |
||
421 | } |
||
422 | |||
423 | 34 | /** |
|
424 | * @return CaptureAggregatorInterface |
||
425 | 34 | */ |
|
426 | protected function getCaptures(): CaptureAggregatorInterface |
||
427 | { |
||
428 | return $this->captures; |
||
429 | } |
||
430 | |||
431 | 34 | /** |
|
432 | * @return ContextStorageInterface |
||
433 | 34 | */ |
|
434 | protected function getContext(): ContextStorageInterface |
||
435 | { |
||
436 | return $this->context; |
||
437 | } |
||
438 | |||
439 | 35 | /** |
|
440 | * @return array |
||
441 | 35 | */ |
|
442 | protected function getValidationBlocks(): array |
||
443 | { |
||
444 | return $this->validationBlocks; |
||
445 | } |
||
446 | |||
447 | 1 | /** |
|
448 | * @return array |
||
449 | 1 | */ |
|
450 | protected function getSerializedRuleSet(): array |
||
451 | { |
||
452 | return $this->serializedRuleSet; |
||
453 | } |
||
454 | |||
455 | 1 | /** |
|
456 | * @return FormatterFactoryInterface |
||
457 | 1 | */ |
|
458 | 1 | protected function getFormatterFactory(): FormatterFactoryInterface |
|
459 | { |
||
460 | return $this->formatterFactory; |
||
461 | 1 | } |
|
462 | |||
463 | /** |
||
464 | * @return FormatterInterface |
||
465 | */ |
||
466 | protected function getFormatter(): FormatterInterface |
||
467 | { |
||
468 | if ($this->formatter === null) { |
||
469 | 17 | $this->formatter = $this->getFormatterFactory()->createFormatter(Messages::NAMESPACE_NAME); |
|
470 | } |
||
471 | 17 | ||
472 | return $this->formatter; |
||
473 | } |
||
474 | |||
475 | /** |
||
476 | * @param string $name |
||
477 | 5 | * |
|
478 | * @return bool |
||
479 | */ |
||
480 | 5 | protected function hasParameter(string $name): bool |
|
481 | { |
||
482 | 5 | return array_key_exists($name, $this->getParameters()); |
|
483 | 5 | } |
|
484 | |||
485 | 5 | /** |
|
486 | * @return mixed |
||
487 | */ |
||
488 | 5 | private function getValidatedIdentity() |
|
489 | 5 | { |
|
490 | 5 | // without validation |
|
491 | $result = $this->getIdentityParameter(); |
||
492 | 5 | ||
493 | $ruleIndexes = $this->serializerClass::readIdentityRuleIndexes($this->getSerializedRuleSet()); |
||
494 | if ($ruleIndexes !== null) { |
||
495 | 5 | // with validation |
|
496 | $ruleIndex = $this->serializerClass::readRuleMainIndex($ruleIndexes); |
||
497 | |||
498 | |||
499 | $this->validationStarts(static::PARAM_IDENTITY, $ruleIndexes); |
||
500 | $this->validateAndThrowOnError(static::PARAM_IDENTITY, $result, $ruleIndex); |
||
501 | $this->validateEnds(static::PARAM_IDENTITY, $ruleIndexes); |
||
502 | |||
503 | 22 | $result = $this->readSingleCapturedValue(); |
|
504 | } |
||
505 | 22 | ||
506 | return $result; |
||
507 | 22 | } |
|
508 | |||
509 | 1 | /** |
|
510 | 1 | * @return iterable |
|
0 ignored issues
–
show
|
|||
511 | * |
||
512 | * @SuppressWarnings(PHPMD.ElseExpression) |
||
513 | */ |
||
514 | 21 | private function getValidatedFilters(): iterable |
|
515 | 21 | { |
|
516 | 21 | $ruleIndexes = $this->serializerClass::readFilterRulesIndexes($this->getSerializedRuleSet()); |
|
517 | 13 | ||
518 | 13 | if ($ruleIndexes === null) { |
|
519 | // without validation |
||
520 | 1 | foreach ($this->getFilterParameters() as $field => $operationsWithArgs) { |
|
521 | 1 | yield $field => $this->parseOperationsAndArguments(static::PARAM_FILTER, $operationsWithArgs); |
|
522 | 1 | } |
|
523 | } else { |
||
524 | // with validation |
||
525 | $mainIndexes = $this->serializerClass::readRuleMainIndexes($ruleIndexes); |
||
526 | 12 | $this->validationStarts(static::PARAM_FILTER, $ruleIndexes); |
|
527 | foreach ($this->getFilterParameters() as $field => $operationsWithArgs) { |
||
528 | 2 | if (is_string($field) === false || empty($field) === true || |
|
529 | 2 | is_array($operationsWithArgs) === false || empty($operationsWithArgs) === true |
|
530 | 2 | ) { |
|
531 | 2 | throw new InvalidQueryParametersException($this->createParameterError( |
|
532 | 2 | static::PARAM_FILTER, |
|
533 | 2 | $this->getInvalidParamMessage() |
|
534 | 2 | )); |
|
535 | } |
||
536 | |||
537 | if (array_key_exists($field, $mainIndexes) === false) { |
||
538 | // unknown field set type |
||
539 | 10 | $this->getValidationErrors()->add( |
|
540 | 10 | new ValidationError( |
|
541 | static::PARAM_FILTER, |
||
542 | 12 | $field, |
|
543 | ErrorCodes::INVALID_VALUE, |
||
544 | Messages::INVALID_VALUE, |
||
545 | 19 | [] |
|
546 | ) |
||
547 | ); |
||
548 | } else { |
||
549 | // for field a validation rule is defined so input value will be validated |
||
550 | $ruleIndex = $mainIndexes[$field]; |
||
551 | $parsed = $this->parseOperationsAndArguments(static::PARAM_FILTER, $operationsWithArgs); |
||
552 | |||
553 | yield $field => $this->validateFilterArguments($ruleIndex, $parsed); |
||
554 | } |
||
555 | } |
||
556 | $this->validateEnds(static::PARAM_FILTER, $ruleIndexes); |
||
557 | 3 | } |
|
558 | } |
||
559 | 3 | ||
560 | /** |
||
561 | 3 | * @param iterable $fieldsFromParent |
|
562 | * |
||
563 | 1 | * @return iterable |
|
0 ignored issues
–
show
|
|||
564 | 1 | * |
|
565 | * @SuppressWarnings(PHPMD.StaticAccess) |
||
566 | * @SuppressWarnings(PHPMD.ElseExpression) |
||
567 | */ |
||
568 | 2 | private function getValidatedFields(iterable $fieldsFromParent): iterable |
|
569 | 2 | { |
|
570 | 2 | $ruleIndexes = $this->serializerClass::readFieldSetRulesIndexes($this->getSerializedRuleSet()); |
|
571 | 2 | ||
572 | 2 | if ($ruleIndexes === null) { |
|
573 | // without validation |
||
574 | foreach ($fieldsFromParent as $type => $fieldList) { |
||
575 | 1 | yield $type => $fieldList; |
|
576 | 1 | } |
|
577 | 1 | } else { |
|
578 | 1 | // with validation |
|
579 | 1 | $mainIndexes = $this->serializerClass::readRuleMainIndexes($ruleIndexes); |
|
580 | 1 | $this->validationStarts(static::PARAM_FIELDS, $ruleIndexes); |
|
581 | 2 | foreach ($fieldsFromParent as $type => $fieldList) { |
|
582 | if (array_key_exists($type, $mainIndexes) === true) { |
||
583 | yield $type => $this->validateValues($mainIndexes[$type], $fieldList); |
||
584 | } else { |
||
585 | // unknown field set type |
||
586 | 2 | $this->getValidationErrors()->add( |
|
587 | new ValidationError( |
||
588 | static::PARAM_FIELDS, |
||
589 | $type, |
||
590 | ErrorCodes::INVALID_VALUE, |
||
591 | Messages::INVALID_VALUE, |
||
592 | [] |
||
593 | ) |
||
594 | ); |
||
595 | } |
||
596 | } |
||
597 | $this->validateEnds(static::PARAM_FIELDS, $ruleIndexes); |
||
598 | 16 | } |
|
599 | } |
||
600 | 16 | ||
601 | /** |
||
602 | 16 | * @param iterable $sortsFromParent |
|
603 | * |
||
604 | 1 | * @return iterable |
|
0 ignored issues
–
show
|
|||
605 | 1 | * |
|
606 | * @SuppressWarnings(PHPMD.StaticAccess) |
||
607 | * @SuppressWarnings(PHPMD.ElseExpression) |
||
608 | */ |
||
609 | 15 | private function getValidatedSorts(iterable $sortsFromParent): iterable |
|
610 | 15 | { |
|
611 | 15 | $ruleIndexes = $this->serializerClass::readSortsRuleIndexes($this->getSerializedRuleSet()); |
|
612 | 5 | ||
613 | 5 | if ($ruleIndexes === null) { |
|
614 | 5 | // without validation |
|
615 | 5 | foreach ($sortsFromParent as $field => $isAsc) { |
|
616 | yield $field => $isAsc; |
||
617 | } |
||
618 | 15 | } else { |
|
619 | // with validation |
||
620 | $ruleIndex = $this->serializerClass::readRuleMainIndex($ruleIndexes); |
||
621 | $this->validationStarts(static::PARAM_SORT, $ruleIndexes); |
||
622 | foreach ($sortsFromParent as $field => $isAsc) { |
||
623 | $this->getCaptures()->clear(); |
||
624 | $this->validateAndAccumulateError($field, $ruleIndex); |
||
625 | if ($this->getCaptures()->count() > 0) { |
||
626 | yield $this->readSingleCapturedValue() => $isAsc; |
||
627 | } |
||
628 | } |
||
629 | $this->validateEnds(static::PARAM_SORT, $ruleIndexes); |
||
630 | 16 | } |
|
631 | } |
||
632 | 16 | ||
633 | /** |
||
634 | 16 | * @param iterable $includesFromParent |
|
635 | * |
||
636 | 1 | * @return iterable |
|
0 ignored issues
–
show
|
|||
637 | 1 | * |
|
638 | * @SuppressWarnings(PHPMD.StaticAccess) |
||
639 | * @SuppressWarnings(PHPMD.ElseExpression) |
||
640 | */ |
||
641 | 15 | private function getValidatedIncludes(iterable $includesFromParent): iterable |
|
642 | 15 | { |
|
643 | 15 | $ruleIndexes = $this->serializerClass::readIncludesRuleIndexes($this->getSerializedRuleSet()); |
|
644 | 4 | ||
645 | 4 | if ($ruleIndexes === null) { |
|
646 | 4 | // without validation |
|
647 | 4 | foreach ($includesFromParent as $path => $split) { |
|
648 | yield $path => $split; |
||
649 | } |
||
650 | 15 | } else { |
|
651 | // with validation |
||
652 | $ruleIndex = $this->serializerClass::readRuleMainIndex($ruleIndexes); |
||
653 | $this->validationStarts(static::PARAM_INCLUDE, $ruleIndexes); |
||
654 | foreach ($includesFromParent as $path => $split) { |
||
655 | $this->getCaptures()->clear(); |
||
656 | $this->validateAndAccumulateError($path, $ruleIndex); |
||
657 | if ($this->getCaptures()->count() > 0) { |
||
658 | yield $this->readSingleCapturedValue() => $split; |
||
659 | 25 | } |
|
660 | } |
||
661 | 25 | $this->validateEnds(static::PARAM_INCLUDE, $ruleIndexes); |
|
662 | } |
||
663 | 25 | } |
|
664 | 19 | ||
665 | /** |
||
666 | * @param iterable $iterable |
||
667 | 21 | * |
|
668 | * @return array |
||
669 | */ |
||
670 | private function iterableToArray(iterable $iterable): array |
||
671 | { |
||
672 | $result = []; |
||
673 | |||
674 | foreach ($iterable as $key => $value) { |
||
675 | 35 | $result[$key] = $value instanceof Generator ? $this->iterableToArray($value) : $value; |
|
676 | } |
||
677 | 35 | ||
678 | 35 | return $result; |
|
679 | } |
||
680 | 35 | ||
681 | /** |
||
682 | * @param mixed $value |
||
683 | * |
||
684 | * @return int |
||
685 | */ |
||
686 | private function validatePageOffset($value): int |
||
687 | { |
||
688 | 35 | $ruleIndexes = $this->serializerClass::readPageOffsetRuleIndexes($this->getSerializedRuleSet()); |
|
689 | $validatedValue = $this->validatePaginationValue($value, $ruleIndexes); |
||
690 | 35 | ||
691 | 35 | return $validatedValue; |
|
692 | } |
||
693 | 35 | ||
694 | /** |
||
695 | * @param mixed $value |
||
696 | * |
||
697 | * @return int |
||
698 | */ |
||
699 | private function validatePageLimit($value): int |
||
700 | { |
||
701 | $ruleIndexes = $this->serializerClass::readPageLimitRuleIndexes($this->getSerializedRuleSet()); |
||
702 | 35 | $validatedValue = $this->validatePaginationValue($value, $ruleIndexes); |
|
703 | |||
704 | return $validatedValue; |
||
705 | 35 | } |
|
706 | 1 | ||
707 | /** |
||
708 | * @param mixed $value |
||
709 | 34 | * @param array $ruleIndexes |
|
710 | * |
||
711 | 34 | * @return int |
|
712 | 34 | */ |
|
713 | 34 | private function validatePaginationValue($value, ?array $ruleIndexes): int |
|
714 | { |
||
715 | 34 | // no validation rule means we should accept any input value |
|
716 | if ($ruleIndexes === null) { |
||
717 | 34 | return is_numeric($value) === true ? (int)$value : 0; |
|
718 | } |
||
719 | |||
720 | $ruleIndex = $this->serializerClass::readRuleMainIndex($ruleIndexes); |
||
721 | |||
722 | $this->validationStarts(static::PARAM_PAGE, $ruleIndexes); |
||
723 | $this->validateAndThrowOnError(static::PARAM_PAGE, $value, $ruleIndex); |
||
724 | $this->validateEnds(static::PARAM_PAGE, $ruleIndexes); |
||
725 | |||
726 | $validatedValue = $this->readSingleCapturedValue(); |
||
727 | |||
728 | 34 | return (int)$validatedValue; |
|
729 | } |
||
730 | 34 | ||
731 | 34 | /** |
|
732 | * @param string $paramName |
||
733 | 34 | * @param array $ruleIndexes |
|
734 | 34 | * |
|
735 | 34 | * @return void |
|
736 | 34 | * |
|
737 | 34 | * @SuppressWarnings(PHPMD.StaticAccess) |
|
738 | */ |
||
739 | 34 | private function validationStarts(string $paramName, array $ruleIndexes): void |
|
740 | { |
||
741 | $this->getCaptures()->clear(); |
||
742 | $this->getValidationErrors()->clear(); |
||
743 | |||
744 | BlockInterpreter::executeStarts( |
||
745 | $this->serializerClass::readRuleStartIndexes($ruleIndexes), |
||
746 | $this->getValidationBlocks(), |
||
747 | $this->getContext(), |
||
748 | $this->getValidationErrors() |
||
749 | ); |
||
750 | $this->checkValidationQueueErrors($paramName); |
||
751 | 34 | } |
|
752 | |||
753 | 34 | /** |
|
754 | 34 | * @param string $paramName |
|
755 | 34 | * @param mixed $value |
|
756 | 34 | * @param int $ruleIndex |
|
757 | 34 | * |
|
758 | 34 | * @return void |
|
759 | 34 | * |
|
760 | * @SuppressWarnings(PHPMD.StaticAccess) |
||
761 | 34 | */ |
|
762 | private function validateAndThrowOnError(string $paramName, $value, int $ruleIndex): void |
||
763 | { |
||
764 | BlockInterpreter::executeBlock( |
||
765 | $value, |
||
766 | $ruleIndex, |
||
767 | $this->getValidationBlocks(), |
||
768 | $this->getContext(), |
||
769 | $this->getCaptures(), |
||
770 | $this->getValidationErrors() |
||
771 | ); |
||
772 | 16 | $this->checkValidationQueueErrors($paramName); |
|
773 | } |
||
774 | 16 | ||
775 | 16 | /** |
|
776 | 16 | * @param mixed $value |
|
777 | 16 | * @param int $ruleIndex |
|
778 | 16 | * |
|
779 | 16 | * @return bool |
|
780 | 16 | * |
|
781 | * @SuppressWarnings(PHPMD.StaticAccess) |
||
782 | */ |
||
783 | private function validateAndAccumulateError($value, int $ruleIndex): bool |
||
784 | { |
||
785 | return BlockInterpreter::executeBlock( |
||
786 | $value, |
||
787 | $ruleIndex, |
||
788 | $this->getValidationBlocks(), |
||
789 | $this->getContext(), |
||
790 | $this->getCaptures(), |
||
791 | $this->getValidationErrors() |
||
792 | 34 | ); |
|
793 | } |
||
794 | 34 | ||
795 | 34 | /** |
|
796 | 34 | * @param string $paramName |
|
797 | 34 | * @param array $ruleIndexes |
|
798 | 34 | * |
|
799 | * @return void |
||
800 | 34 | * |
|
801 | * @SuppressWarnings(PHPMD.StaticAccess) |
||
802 | */ |
||
803 | private function validateEnds(string $paramName, array $ruleIndexes): void |
||
804 | { |
||
805 | BlockInterpreter::executeEnds( |
||
806 | 34 | $this->serializerClass::readRuleEndIndexes($ruleIndexes), |
|
807 | $this->getValidationBlocks(), |
||
808 | 34 | $this->getContext(), |
|
809 | 34 | $this->getValidationErrors() |
|
810 | ); |
||
811 | 34 | $this->checkValidationQueueErrors($paramName); |
|
812 | } |
||
813 | |||
814 | /** |
||
815 | * @return mixed |
||
816 | */ |
||
817 | private function readSingleCapturedValue() |
||
818 | { |
||
819 | assert(count($this->getCaptures()->get()) === 1, 'Expected that only one value would be captured.'); |
||
820 | 10 | $value = current($this->getCaptures()->get()); |
|
821 | |||
822 | 10 | return $value; |
|
823 | 9 | } |
|
824 | 9 | ||
825 | 9 | /** |
|
826 | 9 | * @param int $ruleIndex |
|
827 | * @param iterable $values |
||
828 | * |
||
829 | * @return iterable |
||
0 ignored issues
–
show
|
|||
830 | */ |
||
831 | private function validateValues(int $ruleIndex, iterable $values): iterable |
||
832 | { |
||
833 | foreach ($values as $key => $value) { |
||
834 | $this->getCaptures()->clear(); |
||
835 | $this->validateAndAccumulateError($value, $ruleIndex); |
||
836 | if ($this->getCaptures()->count() > 0) { |
||
837 | 10 | yield $key => $this->readSingleCapturedValue(); |
|
838 | } |
||
839 | 10 | } |
|
840 | 9 | } |
|
841 | |||
842 | /** |
||
843 | * @param int $ruleIndex |
||
844 | * @param iterable $opsAndArgs |
||
845 | * |
||
846 | * @return iterable |
||
0 ignored issues
–
show
|
|||
847 | 35 | */ |
|
848 | private function validateFilterArguments(int $ruleIndex, iterable $opsAndArgs): iterable |
||
849 | 35 | { |
|
850 | 35 | foreach ($opsAndArgs as $operation => $arguments) { |
|
851 | 35 | yield $operation => $this->validateValues($ruleIndex, $arguments); |
|
852 | } |
||
853 | 35 | } |
|
854 | 35 | ||
855 | /** |
||
856 | 35 | * @return self |
|
857 | 35 | */ |
|
858 | private function parsePagingParameters(): self |
||
859 | 35 | { |
|
860 | $parameters = $this->getParameters(); |
||
861 | $mightBeOffset = $parameters[static::PARAM_PAGE][static::PARAM_PAGING_OFFSET] ?? null; |
||
862 | $mightBeLimit = $parameters[static::PARAM_PAGE][static::PARAM_PAGING_LIMIT] ?? null; |
||
863 | |||
864 | $this->pagingOffset = $this->validatePageOffset($mightBeOffset); |
||
865 | $this->pagingLimit = $this->validatePageLimit($mightBeLimit); |
||
866 | |||
867 | assert(is_int($this->pagingOffset) === true && $this->pagingOffset >= 0); |
||
868 | assert(is_int($this->pagingLimit) === true && $this->pagingLimit > 0); |
||
869 | 34 | ||
870 | return $this; |
||
871 | 34 | } |
|
872 | 6 | ||
873 | 6 | /** |
|
874 | * @param string $paramName |
||
875 | * |
||
876 | 6 | * @return void |
|
877 | * |
||
878 | * @throws JsonApiException |
||
879 | */ |
||
880 | private function checkValidationQueueErrors(string $paramName): void |
||
881 | { |
||
882 | if ($this->getValidationErrors()->count() > 0) { |
||
883 | foreach ($this->getValidationErrors()->get() as $error) { |
||
884 | $this->getJsonErrors()->addValidationQueryError($paramName, $error); |
||
885 | 35 | } |
|
886 | |||
887 | 35 | throw new JsonApiException($this->getJsonErrors()); |
|
888 | } |
||
889 | 35 | } |
|
890 | |||
891 | /** |
||
892 | * @param string|null $value |
||
893 | * |
||
894 | * @return self |
||
895 | 5 | */ |
|
896 | private function setIdentityParameter(?string $value): self |
||
897 | 5 | { |
|
898 | $this->identityParameter = $value; |
||
899 | |||
900 | return $this; |
||
901 | } |
||
902 | |||
903 | /** |
||
904 | * @return null|string |
||
905 | 31 | */ |
|
906 | private function getIdentityParameter(): ?string |
||
907 | 31 | { |
|
908 | return $this->identityParameter; |
||
909 | 31 | } |
|
910 | |||
911 | /** |
||
912 | * @param array $values |
||
913 | * |
||
914 | * @return self |
||
915 | 22 | */ |
|
916 | private function setFilterParameters(array $values): self |
||
917 | 22 | { |
|
918 | $this->filterParameters = $values; |
||
919 | |||
920 | return $this; |
||
921 | } |
||
922 | |||
923 | 29 | /** |
|
924 | * @return array |
||
925 | 29 | */ |
|
926 | private function getFilterParameters(): array |
||
927 | 29 | { |
|
928 | return $this->filterParameters; |
||
929 | } |
||
930 | |||
931 | /** |
||
932 | * @return self |
||
933 | 2 | */ |
|
934 | private function setFiltersWithAnd(): self |
||
935 | 2 | { |
|
936 | $this->areFiltersWithAnd = true; |
||
937 | 2 | ||
938 | return $this; |
||
939 | } |
||
940 | |||
941 | /** |
||
942 | * @return self |
||
943 | 38 | */ |
|
944 | private function setFiltersWithOr(): self |
||
945 | 38 | { |
|
946 | 38 | $this->areFiltersWithAnd = false; |
|
947 | 38 | ||
948 | 38 | return $this; |
|
949 | 38 | } |
|
950 | |||
951 | 38 | /** |
|
952 | 38 | * @return self |
|
953 | 38 | */ |
|
954 | 38 | private function clear(): self |
|
955 | 38 | { |
|
956 | $this->identityParameter = null; |
||
957 | 38 | $this->filterParameters = []; |
|
958 | 38 | $this->areFiltersWithAnd = true; |
|
959 | $this->pagingOffset = null; |
||
960 | 38 | $this->pagingLimit = null; |
|
961 | |||
962 | $this->cachedIdentity = null; |
||
963 | $this->cachedFilters = null; |
||
964 | $this->cachedFields = null; |
||
965 | $this->cachedIncludes = null; |
||
966 | $this->cachedSorts = null; |
||
967 | |||
968 | $this->getCaptures()->clear(); |
||
969 | $this->getValidationErrors()->clear(); |
||
970 | |||
971 | 35 | return $this; |
|
972 | } |
||
973 | 35 | ||
974 | 17 | /** |
|
975 | * Pre-parsing for filter parameters. |
||
976 | 17 | * |
|
977 | * @return self |
||
978 | * |
||
979 | 18 | * @SuppressWarnings(PHPMD.ElseExpression) |
|
980 | 18 | * @SuppressWarnings(PHPMD.CyclomaticComplexity) |
|
981 | 2 | */ |
|
982 | 2 | private function parseFilterLink(): self |
|
983 | 2 | { |
|
984 | if (array_key_exists(static::PARAM_FILTER, $this->getParameters()) === false) { |
||
985 | $this->setFiltersWithAnd()->setFilterParameters([]); |
||
986 | |||
987 | 16 | return $this; |
|
988 | 16 | } |
|
989 | |||
990 | $filterSection = $this->getParameters()[static::PARAM_FILTER]; |
||
991 | 16 | if (is_array($filterSection) === false || empty($filterSection) === true) { |
|
992 | 16 | throw new InvalidQueryParametersException($this->createParameterError( |
|
993 | 16 | static::PARAM_FILTER, |
|
994 | 4 | $this->getInvalidParamMessage() |
|
995 | 4 | )); |
|
996 | 4 | } |
|
997 | |||
998 | 2 | $isWithAnd = true; |
|
999 | 2 | reset($filterSection); |
|
1000 | 2 | ||
1001 | // check if top level element is `AND` or `OR` |
||
1002 | $firstKey = key($filterSection); |
||
1003 | 2 | $firstLcKey = strtolower(trim($firstKey)); |
|
1004 | 2 | if (($hasOr = ($firstLcKey === 'or')) || $firstLcKey === 'and') { |
|
1005 | 2 | if (count($filterSection) > 1 || |
|
1006 | empty($filterSection = $filterSection[$firstKey]) === true || |
||
1007 | is_array($filterSection) === false |
||
1008 | ) { |
||
1009 | 12 | throw new InvalidQueryParametersException($this->createParameterError( |
|
1010 | static::PARAM_FILTER, |
||
1011 | $this->getInvalidParamMessage() |
||
1012 | 14 | )); |
|
1013 | } else { |
||
1014 | 14 | $this->setFilterParameters($filterSection); |
|
1015 | if ($hasOr === true) { |
||
1016 | $isWithAnd = false; |
||
1017 | } |
||
1018 | } |
||
1019 | } else { |
||
1020 | $this->setFilterParameters($filterSection); |
||
1021 | } |
||
1022 | |||
1023 | $isWithAnd === true ? $this->setFiltersWithAnd() : $this->setFiltersWithOr(); |
||
1024 | |||
1025 | 11 | return $this; |
|
1026 | } |
||
1027 | |||
1028 | 11 | /** |
|
1029 | 11 | * @param string $parameterName |
|
1030 | 11 | * @param array $value |
|
1031 | * |
||
1032 | 1 | * @return iterable |
|
0 ignored issues
–
show
|
|||
1033 | 1 | * |
|
1034 | 1 | * @SuppressWarnings(PHPMD.ElseExpression) |
|
1035 | */ |
||
1036 | private function parseOperationsAndArguments(string $parameterName, array $value): iterable |
||
1037 | 10 | { |
|
1038 | 1 | // in this case we interpret it as an [operation => 'comma separated argument(s)'] |
|
1039 | foreach ($value as $operationName => $arguments) { |
||
1040 | 9 | if (is_string($operationName) === false || empty($operationName) === true || |
|
1041 | 9 | is_string($arguments) === false |
|
1042 | 9 | ) { |
|
1043 | 10 | $title = $this->getFormatter()->formatMessage(Messages::INVALID_OPERATION_ARGUMENTS); |
|
1044 | $error = $this->createQueryError($parameterName, $title); |
||
1045 | throw new InvalidQueryParametersException($error); |
||
1046 | } |
||
1047 | |||
1048 | if ($arguments === '') { |
||
1049 | yield $operationName => []; |
||
1050 | } else { |
||
1051 | yield $operationName => $this->splitCommaSeparatedStringAndCheckNoEmpties( |
||
1052 | $parameterName, |
||
1053 | $arguments, |
||
1054 | $this->getInvalidParamMessage() |
||
1055 | 1 | ); |
|
1056 | } |
||
1057 | 1 | } |
|
1058 | 1 | } |
|
1059 | |||
1060 | 1 | /** |
|
1061 | * @param string $paramName |
||
1062 | * @param string $errorTitle |
||
1063 | * |
||
1064 | * @return ErrorInterface |
||
1065 | */ |
||
1066 | private function createQueryError(string $paramName, string $errorTitle): ErrorInterface |
||
1067 | 25 | { |
|
1068 | $source = [ErrorInterface::SOURCE_PARAMETER => $paramName]; |
||
1069 | 25 | $error = new JsonApiError(null, null, null, null, null, $errorTitle, null, $source); |
|
1070 | |||
1071 | return $error; |
||
1072 | } |
||
1073 | |||
1074 | /** |
||
1075 | * |
||
1076 | * @return string |
||
1077 | */ |
||
1078 | private function getInvalidParamMessage(): string |
||
1079 | { |
||
1080 | return $this->getMessage(static::MSG_ERR_INVALID_PARAMETER); |
||
1081 | } |
||
1082 | } |
||
1083 |
This check looks at variables that have been passed in as parameters and are passed out again to other methods.
If the outgoing method call has stricter type requirements than the method itself, an issue is raised.
An additional type check may prevent trouble.