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); |
|
|
|
|
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)); |
|
|
|
|
216
|
35 |
|
$this->serializedRuleSet = $this->serializerClass::readRules($rulesClass, $serializedData); |
|
|
|
|
217
|
|
|
$this->validationBlocks = $this->serializerClass::readBlocks($serializedData); |
|
|
|
|
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()); |
|
|
|
|
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)); |
|
|
|
|
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)); |
|
|
|
|
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)); |
|
|
|
|
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 |
|
|
|
|
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 |
|
|
|
|
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 |
|
|
|
|
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 |
|
|
|
|
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 |
|
|
|
|
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 |
|
|
|
|
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 |
|
|
|
|
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.