|
1
|
|
|
<?php namespace Limoncello\Flute\Http\Query; |
|
2
|
|
|
|
|
3
|
|
|
/** |
|
4
|
|
|
* Copyright 2015-2017 [email protected] |
|
5
|
|
|
* |
|
6
|
|
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
|
7
|
|
|
* you may not use this file except in compliance with the License. |
|
8
|
|
|
* You may obtain a copy of the License at |
|
9
|
|
|
* |
|
10
|
|
|
* http://www.apache.org/licenses/LICENSE-2.0 |
|
11
|
|
|
* |
|
12
|
|
|
* Unless required by applicable law or agreed to in writing, software |
|
13
|
|
|
* distributed under the License is distributed on an "AS IS" BASIS, |
|
14
|
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|
15
|
|
|
* See the License for the specific language governing permissions and |
|
16
|
|
|
* limitations under the License. |
|
17
|
|
|
*/ |
|
18
|
|
|
|
|
19
|
|
|
use Limoncello\Container\Traits\HasContainerTrait; |
|
20
|
|
|
use Limoncello\Flute\Contracts\Adapters\PaginationStrategyInterface; |
|
21
|
|
|
use Limoncello\Flute\Contracts\Http\Query\QueryParserInterface; |
|
22
|
|
|
use Limoncello\Flute\Contracts\Http\Query\QueryValidatorInterface; |
|
23
|
|
|
use Limoncello\Flute\Contracts\Validation\ErrorCodes; |
|
24
|
|
|
use Limoncello\Flute\Exceptions\InvalidQueryParametersException; |
|
25
|
|
|
use Limoncello\Flute\Validation\Form\Execution\FormRuleSerializer; |
|
26
|
|
|
use Limoncello\Validation\Captures\CaptureAggregator; |
|
27
|
|
|
use Limoncello\Validation\Contracts\Captures\CaptureAggregatorInterface; |
|
28
|
|
|
use Limoncello\Validation\Contracts\Errors\ErrorAggregatorInterface; |
|
29
|
|
|
use Limoncello\Validation\Contracts\Execution\ContextStorageInterface; |
|
30
|
|
|
use Limoncello\Validation\Errors\Error; |
|
31
|
|
|
use Limoncello\Validation\Errors\ErrorAggregator; |
|
32
|
|
|
use Limoncello\Validation\Execution\BlockInterpreter; |
|
33
|
|
|
use Limoncello\Validation\Execution\ContextStorage; |
|
34
|
|
|
use Psr\Container\ContainerInterface; |
|
35
|
|
|
|
|
36
|
|
|
/** |
|
37
|
|
|
* @package Limoncello\Flute |
|
38
|
|
|
* |
|
39
|
|
|
* @SuppressWarnings(PHPMD.CouplingBetweenObjects) |
|
40
|
|
|
*/ |
|
41
|
|
|
class QueryValidator extends QueryParser implements QueryValidatorInterface |
|
42
|
|
|
{ |
|
43
|
|
|
use HasContainerTrait; |
|
44
|
|
|
|
|
45
|
|
|
/** |
|
46
|
|
|
* @var ContextStorageInterface |
|
47
|
|
|
*/ |
|
48
|
|
|
private $contextStorage; |
|
49
|
|
|
|
|
50
|
|
|
/** |
|
51
|
|
|
* @var CaptureAggregatorInterface |
|
52
|
|
|
*/ |
|
53
|
|
|
private $captureAggregator; |
|
54
|
|
|
|
|
55
|
|
|
/** |
|
56
|
|
|
* @var ErrorAggregatorInterface |
|
57
|
|
|
*/ |
|
58
|
|
|
private $errorAggregator; |
|
59
|
|
|
|
|
60
|
|
|
/** |
|
61
|
|
|
* @var array |
|
62
|
|
|
*/ |
|
63
|
|
|
private $rulesData; |
|
64
|
|
|
|
|
65
|
|
|
/** |
|
66
|
|
|
* @var array |
|
67
|
|
|
*/ |
|
68
|
|
|
private $blocks; |
|
69
|
|
|
|
|
70
|
|
|
/** |
|
71
|
|
|
* @var int[] |
|
72
|
|
|
*/ |
|
73
|
|
|
private $attributeRules; |
|
74
|
|
|
|
|
75
|
|
|
/** |
|
76
|
|
|
* @var array |
|
77
|
|
|
*/ |
|
78
|
|
|
private $attributeRulesIdx; |
|
79
|
|
|
|
|
80
|
|
|
/** |
|
81
|
|
|
* @param array $data |
|
82
|
|
|
* @param ContainerInterface $container |
|
83
|
|
|
* @param PaginationStrategyInterface $paginationStrategy |
|
84
|
|
|
* @param string[]|null $messages |
|
85
|
|
|
* |
|
86
|
|
|
* @SuppressWarnings(PHPMD.StaticAccess) |
|
87
|
|
|
*/ |
|
88
|
5 |
|
public function __construct( |
|
89
|
|
|
array $data, |
|
90
|
|
|
ContainerInterface $container, |
|
91
|
|
|
PaginationStrategyInterface $paginationStrategy, |
|
92
|
|
|
array $messages = null |
|
93
|
|
|
) { |
|
94
|
|
|
$this |
|
95
|
5 |
|
->setContainer($container) |
|
96
|
5 |
|
->setRulesData($data) |
|
97
|
5 |
|
->setBlocks(FormRuleSerializer::extractBlocks($this->getRulesData())) |
|
98
|
5 |
|
->setContextStorage($this->createContextStorage()) |
|
99
|
5 |
|
->setCaptureAggregator($this->createCaptureAggregator()) |
|
100
|
5 |
|
->setErrorAggregator($this->createErrorAggregator()); |
|
101
|
|
|
|
|
102
|
5 |
|
parent::__construct($paginationStrategy, $messages); |
|
103
|
|
|
} |
|
104
|
|
|
|
|
105
|
|
|
/** |
|
106
|
|
|
* @inheritdoc |
|
107
|
|
|
*/ |
|
108
|
4 |
|
public function withAllAllowedFilterFields(): QueryParserInterface |
|
109
|
|
|
{ |
|
110
|
4 |
|
$self = parent::withAllAllowedFilterFields(); |
|
111
|
|
|
|
|
112
|
4 |
|
$this->unsetAttributeRules(); |
|
113
|
|
|
|
|
114
|
4 |
|
return $self; |
|
115
|
|
|
} |
|
116
|
|
|
|
|
117
|
|
|
/** |
|
118
|
|
|
* @inheritdoc |
|
119
|
|
|
*/ |
|
120
|
5 |
|
public function withNoAllowedFilterFields(): QueryParserInterface |
|
121
|
|
|
{ |
|
122
|
5 |
|
$self = parent::withNoAllowedFilterFields(); |
|
123
|
|
|
|
|
124
|
5 |
|
$this->unsetAttributeRules(); |
|
125
|
|
|
|
|
126
|
5 |
|
return $self; |
|
127
|
|
|
} |
|
128
|
|
|
|
|
129
|
|
|
/** |
|
130
|
|
|
* @inheritdoc |
|
131
|
|
|
*/ |
|
132
|
1 |
|
public function withAllowedFilterFields(array $fields): QueryParserInterface |
|
133
|
|
|
{ |
|
134
|
1 |
|
$self = parent::withAllowedFilterFields($fields); |
|
135
|
|
|
|
|
136
|
1 |
|
$this->unsetAttributeRules(); |
|
137
|
|
|
|
|
138
|
1 |
|
return $self; |
|
139
|
|
|
} |
|
140
|
|
|
|
|
141
|
|
|
/** |
|
142
|
|
|
* @inheritdoc |
|
143
|
|
|
* |
|
144
|
|
|
* @SuppressWarnings(PHPMD.StaticAccess) |
|
145
|
|
|
*/ |
|
146
|
4 |
|
public function withValidatedFilterFields(string $rulesSetClass): QueryValidatorInterface |
|
147
|
|
|
{ |
|
148
|
4 |
|
$this->withAllAllowedFilterFields(); |
|
149
|
|
|
|
|
150
|
4 |
|
return $this->setAttributeRules(FormRuleSerializer::getAttributeRules($rulesSetClass, $this->getRulesData())); |
|
151
|
|
|
} |
|
152
|
|
|
|
|
153
|
|
|
/** |
|
154
|
|
|
* @inheritdoc |
|
155
|
|
|
* |
|
156
|
|
|
* @SuppressWarnings(PHPMD.StaticAccess) |
|
157
|
|
|
* @SuppressWarnings(PHPMD.ElseExpression) |
|
158
|
|
|
*/ |
|
159
|
4 |
|
public function getFilters(): iterable |
|
160
|
|
|
{ |
|
161
|
4 |
|
$filters = parent::getFilters(); |
|
162
|
|
|
|
|
163
|
4 |
|
$serializedRules = $this->getAttributeRules(); |
|
164
|
|
|
|
|
165
|
|
|
// if validation rules were actually set |
|
166
|
4 |
|
if ($serializedRules !== null) { |
|
167
|
3 |
|
$this->executeStarts(FormRuleSerializer::getRulesStartIndexes($serializedRules)); |
|
168
|
|
|
|
|
169
|
3 |
|
foreach ($filters as $field => $operationsAndArgs) { |
|
170
|
3 |
|
if (($index = $this->getAttributeIndex($field)) !== null) { |
|
171
|
2 |
|
yield $field => $this->getValidatedOperationsAndArguments($index, $field, $operationsAndArgs); |
|
172
|
|
|
} else { |
|
173
|
|
|
// unknown field |
|
174
|
1 |
|
$value = null; |
|
175
|
1 |
|
$context = null; |
|
176
|
3 |
|
$this->getErrorAggregator()->add(new Error($field, $value, ErrorCodes::INVALID_VALUE, $context)); |
|
177
|
|
|
} |
|
178
|
|
|
} |
|
179
|
|
|
|
|
180
|
3 |
|
$this->executeEnds(FormRuleSerializer::getRulesEndIndexes($this->getAttributeRules())); |
|
|
|
|
|
|
181
|
|
|
|
|
182
|
3 |
|
if ($this->getErrorAggregator()->count() > 0) { |
|
183
|
3 |
|
throw new InvalidQueryParametersException($this->createParameterError(static::PARAM_FILTER)); |
|
184
|
|
|
} |
|
185
|
|
|
} else { |
|
186
|
1 |
|
foreach ($filters as $field => $operationsAndArgs) { |
|
187
|
1 |
|
yield $field => $operationsAndArgs; |
|
188
|
|
|
} |
|
189
|
|
|
} |
|
190
|
|
|
} |
|
191
|
|
|
|
|
192
|
|
|
/** |
|
193
|
|
|
* @return ContextStorageInterface |
|
194
|
|
|
*/ |
|
195
|
5 |
|
protected function createContextStorage(): ContextStorageInterface |
|
196
|
|
|
{ |
|
197
|
5 |
|
return new ContextStorage($this->getBlocks(), $this->getContainer()); |
|
198
|
|
|
} |
|
199
|
|
|
|
|
200
|
|
|
/** |
|
201
|
|
|
* @return CaptureAggregatorInterface |
|
202
|
|
|
*/ |
|
203
|
5 |
|
protected function createCaptureAggregator(): CaptureAggregatorInterface |
|
204
|
|
|
{ |
|
205
|
5 |
|
return new CaptureAggregator(); |
|
206
|
|
|
} |
|
207
|
|
|
|
|
208
|
|
|
/** |
|
209
|
|
|
* @return ErrorAggregatorInterface |
|
210
|
|
|
*/ |
|
211
|
5 |
|
protected function createErrorAggregator(): ErrorAggregatorInterface |
|
212
|
|
|
{ |
|
213
|
5 |
|
return new ErrorAggregator(); |
|
214
|
|
|
} |
|
215
|
|
|
|
|
216
|
|
|
/** |
|
217
|
|
|
* @param ContextStorageInterface $contextStorage |
|
218
|
|
|
* |
|
219
|
|
|
* @return self |
|
220
|
|
|
*/ |
|
221
|
5 |
|
private function setContextStorage(ContextStorageInterface $contextStorage): self |
|
222
|
|
|
{ |
|
223
|
5 |
|
$this->contextStorage = $contextStorage; |
|
224
|
|
|
|
|
225
|
5 |
|
return $this; |
|
226
|
|
|
} |
|
227
|
|
|
|
|
228
|
|
|
/** |
|
229
|
|
|
* @return CaptureAggregatorInterface |
|
230
|
|
|
*/ |
|
231
|
2 |
|
private function getCaptureAggregator(): CaptureAggregatorInterface |
|
232
|
|
|
{ |
|
233
|
2 |
|
return $this->captureAggregator; |
|
234
|
|
|
} |
|
235
|
|
|
|
|
236
|
|
|
/** |
|
237
|
|
|
* @param CaptureAggregatorInterface $captureAggregator |
|
238
|
|
|
* |
|
239
|
|
|
* @return self |
|
240
|
|
|
*/ |
|
241
|
5 |
|
private function setCaptureAggregator(CaptureAggregatorInterface $captureAggregator): self |
|
242
|
|
|
{ |
|
243
|
5 |
|
$this->captureAggregator = $captureAggregator; |
|
244
|
|
|
|
|
245
|
5 |
|
return $this; |
|
246
|
|
|
} |
|
247
|
|
|
|
|
248
|
|
|
/** |
|
249
|
|
|
* @return ErrorAggregatorInterface |
|
250
|
|
|
*/ |
|
251
|
3 |
|
private function getErrorAggregator(): ErrorAggregatorInterface |
|
252
|
|
|
{ |
|
253
|
3 |
|
return $this->errorAggregator; |
|
254
|
|
|
} |
|
255
|
|
|
|
|
256
|
|
|
/** |
|
257
|
|
|
* @param ErrorAggregatorInterface $errorAggregator |
|
258
|
|
|
* |
|
259
|
|
|
* @return self |
|
260
|
|
|
*/ |
|
261
|
5 |
|
private function setErrorAggregator(ErrorAggregatorInterface $errorAggregator): self |
|
262
|
|
|
{ |
|
263
|
5 |
|
$this->errorAggregator = $errorAggregator; |
|
264
|
|
|
|
|
265
|
5 |
|
return $this; |
|
266
|
|
|
} |
|
267
|
|
|
|
|
268
|
|
|
/** |
|
269
|
|
|
* @param int $blockIndex |
|
270
|
|
|
* @param string $name |
|
271
|
|
|
* @param iterable $operationsAndArgs |
|
272
|
|
|
* |
|
273
|
|
|
* @return iterable |
|
|
|
|
|
|
274
|
|
|
*/ |
|
275
|
2 |
|
private function getValidatedOperationsAndArguments( |
|
276
|
|
|
int $blockIndex, |
|
277
|
|
|
string $name, |
|
278
|
|
|
iterable $operationsAndArgs |
|
279
|
|
|
): iterable { |
|
280
|
2 |
|
foreach ($operationsAndArgs as $operation => $args) { |
|
281
|
2 |
|
yield $operation => $this->getValidatedArguments($blockIndex, $name, $args); |
|
282
|
|
|
} |
|
283
|
|
|
} |
|
284
|
|
|
|
|
285
|
|
|
/** |
|
286
|
|
|
* @param int $blockIndex |
|
287
|
|
|
* @param string $name |
|
288
|
|
|
* @param iterable $arguments |
|
289
|
|
|
* |
|
290
|
|
|
* @return iterable |
|
|
|
|
|
|
291
|
|
|
*/ |
|
292
|
2 |
|
private function getValidatedArguments(int $blockIndex, string $name, iterable $arguments): iterable |
|
293
|
|
|
{ |
|
294
|
2 |
|
foreach ($arguments as $argument) { |
|
295
|
2 |
|
if ($this->executeBlock($argument, $blockIndex) === true) { |
|
296
|
2 |
|
$validated = $this->getCaptureAggregator()->get()[$name]; |
|
297
|
2 |
|
yield $validated; |
|
298
|
|
|
} |
|
299
|
|
|
} |
|
300
|
|
|
} |
|
301
|
|
|
|
|
302
|
|
|
/** |
|
303
|
|
|
* @return ContextStorageInterface |
|
304
|
|
|
*/ |
|
305
|
3 |
|
private function getContextStorage(): ContextStorageInterface |
|
306
|
|
|
{ |
|
307
|
3 |
|
return $this->contextStorage; |
|
308
|
|
|
} |
|
309
|
|
|
|
|
310
|
|
|
/** |
|
311
|
|
|
* @return array |
|
312
|
|
|
*/ |
|
313
|
5 |
|
private function getRulesData(): array |
|
314
|
|
|
{ |
|
315
|
5 |
|
return $this->rulesData; |
|
316
|
|
|
} |
|
317
|
|
|
|
|
318
|
|
|
/** |
|
319
|
|
|
* @param array $rulesData |
|
320
|
|
|
* |
|
321
|
|
|
* @return self |
|
322
|
|
|
*/ |
|
323
|
5 |
|
private function setRulesData(array $rulesData): self |
|
324
|
|
|
{ |
|
325
|
5 |
|
$this->rulesData = $rulesData; |
|
326
|
|
|
|
|
327
|
5 |
|
return $this; |
|
328
|
|
|
} |
|
329
|
|
|
|
|
330
|
|
|
/** |
|
331
|
|
|
* @return array |
|
332
|
|
|
*/ |
|
333
|
5 |
|
private function getBlocks(): array |
|
334
|
|
|
{ |
|
335
|
5 |
|
return $this->blocks; |
|
336
|
|
|
} |
|
337
|
|
|
|
|
338
|
|
|
/** |
|
339
|
|
|
* @param array $blocks |
|
340
|
|
|
* |
|
341
|
|
|
* @return self |
|
342
|
|
|
*/ |
|
343
|
5 |
|
private function setBlocks(array $blocks): self |
|
344
|
|
|
{ |
|
345
|
5 |
|
$this->blocks = $blocks; |
|
346
|
|
|
|
|
347
|
5 |
|
return $this; |
|
348
|
|
|
} |
|
349
|
|
|
|
|
350
|
|
|
/** |
|
351
|
|
|
* @param array $rules |
|
352
|
|
|
* |
|
353
|
|
|
* @return self |
|
354
|
|
|
* |
|
355
|
|
|
* @SuppressWarnings(PHPMD.StaticAccess) |
|
356
|
|
|
*/ |
|
357
|
4 |
|
private function setAttributeRules(array $rules): self |
|
358
|
|
|
{ |
|
359
|
4 |
|
assert($this->debugCheckIndexesExist($rules)); |
|
360
|
|
|
|
|
361
|
4 |
|
$this->attributeRules = $rules; |
|
362
|
4 |
|
$this->attributeRulesIdx = FormRuleSerializer::getRulesIndexes($rules); |
|
363
|
|
|
|
|
364
|
4 |
|
return $this; |
|
365
|
|
|
} |
|
366
|
|
|
|
|
367
|
|
|
/** |
|
368
|
|
|
* @return self |
|
369
|
|
|
*/ |
|
370
|
5 |
|
private function unsetAttributeRules(): self |
|
371
|
|
|
{ |
|
372
|
5 |
|
$this->attributeRules = null; |
|
|
|
|
|
|
373
|
5 |
|
$this->attributeRulesIdx = null; |
|
|
|
|
|
|
374
|
|
|
|
|
375
|
5 |
|
return $this; |
|
376
|
|
|
} |
|
377
|
|
|
|
|
378
|
|
|
/** |
|
379
|
|
|
* @return int[]|null |
|
380
|
|
|
*/ |
|
381
|
4 |
|
private function getAttributeRules(): ?array |
|
382
|
|
|
{ |
|
383
|
4 |
|
return $this->attributeRules; |
|
384
|
|
|
} |
|
385
|
|
|
|
|
386
|
|
|
/** |
|
387
|
|
|
* @param string $name |
|
388
|
|
|
* |
|
389
|
|
|
* @return int|null |
|
390
|
|
|
* |
|
391
|
|
|
* @SuppressWarnings(PHPMD.StaticAccess) |
|
392
|
|
|
*/ |
|
393
|
3 |
|
private function getAttributeIndex(string $name): ?int |
|
394
|
|
|
{ |
|
395
|
3 |
|
$index = $this->attributeRulesIdx[$name] ?? null; |
|
396
|
|
|
|
|
397
|
3 |
|
return $index; |
|
398
|
|
|
} |
|
399
|
|
|
|
|
400
|
|
|
/** |
|
401
|
|
|
* @param array $indexes |
|
402
|
|
|
* |
|
403
|
|
|
* @return bool |
|
404
|
|
|
* |
|
405
|
|
|
* @SuppressWarnings(PHPMD.StaticAccess) |
|
406
|
|
|
*/ |
|
407
|
3 |
|
private function executeStarts(array $indexes): bool |
|
408
|
|
|
{ |
|
409
|
3 |
|
return BlockInterpreter::executeStarts( |
|
410
|
3 |
|
$indexes, |
|
411
|
3 |
|
$this->getBlocks(), |
|
412
|
3 |
|
$this->getContextStorage(), |
|
413
|
3 |
|
$this->getErrorAggregator() |
|
414
|
|
|
); |
|
415
|
|
|
} |
|
416
|
|
|
|
|
417
|
|
|
/** |
|
418
|
|
|
* @param array $indexes |
|
419
|
|
|
* |
|
420
|
|
|
* @return bool |
|
421
|
|
|
* |
|
422
|
|
|
* @SuppressWarnings(PHPMD.StaticAccess) |
|
423
|
|
|
*/ |
|
424
|
3 |
|
private function executeEnds(array $indexes): bool |
|
425
|
|
|
{ |
|
426
|
3 |
|
return BlockInterpreter::executeEnds( |
|
427
|
3 |
|
$indexes, |
|
428
|
3 |
|
$this->getBlocks(), |
|
429
|
3 |
|
$this->getContextStorage(), |
|
430
|
3 |
|
$this->getErrorAggregator() |
|
431
|
|
|
); |
|
432
|
|
|
} |
|
433
|
|
|
|
|
434
|
|
|
/** |
|
435
|
|
|
* @param mixed $input |
|
436
|
|
|
* @param int $index |
|
437
|
|
|
* |
|
438
|
|
|
* @return bool |
|
439
|
|
|
* |
|
440
|
|
|
* @SuppressWarnings(PHPMD.StaticAccess) |
|
441
|
|
|
*/ |
|
442
|
2 |
|
private function executeBlock($input, int $index): bool |
|
443
|
|
|
{ |
|
444
|
2 |
|
return BlockInterpreter::executeBlock( |
|
445
|
2 |
|
$input, |
|
446
|
2 |
|
$index, |
|
447
|
2 |
|
$this->getBlocks(), |
|
448
|
2 |
|
$this->getContextStorage(), |
|
449
|
2 |
|
$this->getCaptureAggregator(), |
|
450
|
2 |
|
$this->getErrorAggregator() |
|
451
|
|
|
); |
|
452
|
|
|
} |
|
453
|
|
|
|
|
454
|
|
|
/** |
|
455
|
|
|
* @param array $rules |
|
456
|
|
|
* |
|
457
|
|
|
* @return bool |
|
458
|
|
|
* |
|
459
|
|
|
* @SuppressWarnings(PHPMD.StaticAccess) |
|
460
|
|
|
*/ |
|
461
|
4 |
|
private function debugCheckIndexesExist(array $rules): bool |
|
462
|
|
|
{ |
|
463
|
4 |
|
$allOk = true; |
|
464
|
|
|
|
|
465
|
4 |
|
$indexes = array_merge( |
|
466
|
4 |
|
FormRuleSerializer::getRulesIndexes($rules), |
|
467
|
4 |
|
FormRuleSerializer::getRulesStartIndexes($rules), |
|
468
|
4 |
|
FormRuleSerializer::getRulesEndIndexes($rules) |
|
469
|
|
|
); |
|
470
|
|
|
|
|
471
|
4 |
|
foreach ($indexes as $index) { |
|
472
|
3 |
|
$allOk = $allOk && is_int($index) && FormRuleSerializer::isRuleExist($index, $this->getBlocks()); |
|
473
|
|
|
} |
|
474
|
|
|
|
|
475
|
4 |
|
return $allOk; |
|
476
|
|
|
} |
|
477
|
|
|
} |
|
478
|
|
|
|
This check looks at variables that 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.