Completed
Push — master ( fb30fb...b9dc46 )
by Neomerx
13:06
created

QueryValidator::setErrorAggregator()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 1

Importance

Changes 0
Metric Value
dl 0
loc 6
c 0
b 0
f 0
ccs 3
cts 3
cp 1
rs 9.4285
cc 1
eloc 3
nc 1
nop 1
crap 1
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\AttributeRulesSerializer;
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(AttributeRulesSerializer::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 5
    public function withAllAllowedFilterFields(): QueryParserInterface
109
    {
110 5
        $self = parent::withAllAllowedFilterFields();
111
112 5
        $this->unsetAttributeRules();
113
114 5
        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 5
    public function withValidatedFilterFields(string $rulesSetClass): QueryValidatorInterface
147
    {
148 5
        $this->withAllAllowedFilterFields();
149
150 5
        return $this->setAttributeRules(
151 5
            AttributeRulesSerializer::getAttributeRules($rulesSetClass, $this->getRulesData())
152
        );
153
    }
154
155
    /**
156
     * @inheritdoc
157
     *
158
     * @SuppressWarnings(PHPMD.StaticAccess)
159
     * @SuppressWarnings(PHPMD.ElseExpression)
160
     */
161 4
    public function getFilters(): iterable
162
    {
163 4
        $filters = parent::getFilters();
164
165 4
        $serializedRules = $this->getAttributeRules();
166
167
        // if validation rules were actually set
168 4
        if ($serializedRules !== null) {
169 3
            $this->executeStarts(AttributeRulesSerializer::getRulesStartIndexes($serializedRules));
170
171 3
            foreach ($filters as $field => $operationsAndArgs) {
172 3
                if (($index = $this->getAttributeIndex($field)) !== null) {
173 2
                    yield $field => $this->getValidatedOperationsAndArguments($index, $field, $operationsAndArgs);
174
                } else {
175
                    // unknown field
176 1
                    $value   = null;
177 1
                    $context = null;
178 3
                    $this->getErrorAggregator()->add(new Error($field, $value, ErrorCodes::INVALID_VALUE, $context));
179
                }
180
            }
181
182 3
            $this->executeEnds(AttributeRulesSerializer::getRulesEndIndexes($this->getAttributeRules()));
0 ignored issues
show
Bug introduced by
It seems like $this->getAttributeRules() targeting Limoncello\Flute\Http\Qu...or::getAttributeRules() can also be of type null; however, Limoncello\Flute\Validat...r::getRulesEndIndexes() does only seem to accept array, maybe add an additional type check?

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.

Loading history...
183
184 3
            if ($this->getErrorAggregator()->count() > 0) {
185 3
                throw new InvalidQueryParametersException($this->createParameterError(static::PARAM_FILTER));
186
            }
187
        } else {
188 1
            foreach ($filters as $field => $operationsAndArgs) {
189 1
                yield $field => $operationsAndArgs;
190
            }
191
        }
192
    }
193
194
    /**
195
     * @return ContextStorageInterface
196
     */
197 5
    protected function createContextStorage(): ContextStorageInterface
198
    {
199 5
        return new ContextStorage($this->getBlocks(), $this->getContainer());
200
    }
201
202
    /**
203
     * @return CaptureAggregatorInterface
204
     */
205 5
    protected function createCaptureAggregator(): CaptureAggregatorInterface
206
    {
207 5
        return new CaptureAggregator();
208
    }
209
210
    /**
211
     * @return ErrorAggregatorInterface
212
     */
213 5
    protected function createErrorAggregator(): ErrorAggregatorInterface
214
    {
215 5
        return new ErrorAggregator();
216
    }
217
218
    /**
219
     * @param ContextStorageInterface $contextStorage
220
     *
221
     * @return self
222
     */
223 5
    private function setContextStorage(ContextStorageInterface $contextStorage): self
224
    {
225 5
        $this->contextStorage = $contextStorage;
226
227 5
        return $this;
228
    }
229
230
    /**
231
     * @return CaptureAggregatorInterface
232
     */
233 2
    private function getCaptureAggregator(): CaptureAggregatorInterface
234
    {
235 2
        return $this->captureAggregator;
236
    }
237
238
    /**
239
     * @param CaptureAggregatorInterface $captureAggregator
240
     *
241
     * @return self
242
     */
243 5
    private function setCaptureAggregator(CaptureAggregatorInterface $captureAggregator): self
244
    {
245 5
        $this->captureAggregator = $captureAggregator;
246
247 5
        return $this;
248
    }
249
250
    /**
251
     * @return ErrorAggregatorInterface
252
     */
253 3
    private function getErrorAggregator(): ErrorAggregatorInterface
254
    {
255 3
        return $this->errorAggregator;
256
    }
257
258
    /**
259
     * @param ErrorAggregatorInterface $errorAggregator
260
     *
261
     * @return self
262
     */
263 5
    private function setErrorAggregator(ErrorAggregatorInterface $errorAggregator): self
264
    {
265 5
        $this->errorAggregator = $errorAggregator;
266
267 5
        return $this;
268
    }
269
270
    /**
271
     * @param int      $blockIndex
272
     * @param string   $name
273
     * @param iterable $operationsAndArgs
274
     *
275
     * @return iterable
0 ignored issues
show
Documentation introduced by
Should the return type not be \Generator?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
276
     */
277 2
    private function getValidatedOperationsAndArguments(
278
        int $blockIndex,
279
        string $name,
280
        iterable $operationsAndArgs
281
    ): iterable {
282 2
        foreach ($operationsAndArgs as $operation => $args) {
283 2
            yield $operation => $this->getValidatedArguments($blockIndex, $name, $args);
284
        }
285
    }
286
287
    /**
288
     * @param int      $blockIndex
289
     * @param string   $name
290
     * @param iterable $arguments
291
     *
292
     * @return iterable
0 ignored issues
show
Documentation introduced by
Should the return type not be \Generator?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
293
     */
294 2
    private function getValidatedArguments(int $blockIndex, string $name, iterable $arguments): iterable
295
    {
296 2
        foreach ($arguments as $argument) {
297 2
            if ($this->executeBlock($argument, $blockIndex) === true) {
298 2
                $validated = $this->getCaptureAggregator()->get()[$name];
299 2
                yield $validated;
300
            }
301
        }
302
    }
303
304
    /**
305
     * @return ContextStorageInterface
306
     */
307 3
    private function getContextStorage(): ContextStorageInterface
308
    {
309 3
        return $this->contextStorage;
310
    }
311
312
    /**
313
     * @return array
314
     */
315 5
    private function getRulesData(): array
316
    {
317 5
        return $this->rulesData;
318
    }
319
320
    /**
321
     * @param array $rulesData
322
     *
323
     * @return self
324
     */
325 5
    private function setRulesData(array $rulesData): self
326
    {
327 5
        $this->rulesData = $rulesData;
328
329 5
        return $this;
330
    }
331
332
    /**
333
     * @return array
334
     */
335 5
    private function getBlocks(): array
336
    {
337 5
        return $this->blocks;
338
    }
339
340
    /**
341
     * @param array $blocks
342
     *
343
     * @return self
344
     */
345 5
    private function setBlocks(array $blocks): self
346
    {
347 5
        $this->blocks = $blocks;
348
349 5
        return $this;
350
    }
351
352
    /**
353
     * @param array $rules
354
     *
355
     * @return self
356
     *
357
     * @SuppressWarnings(PHPMD.StaticAccess)
358
     */
359 5
    private function setAttributeRules(array $rules): self
360
    {
361 5
        assert($this->debugCheckIndexesExist($rules));
362
363 5
        $this->attributeRules    = $rules;
364 5
        $this->attributeRulesIdx = AttributeRulesSerializer::getRulesIndexes($rules);
365
366 5
        return $this;
367
    }
368
369
    /**
370
     * @return self
371
     */
372 5
    private function unsetAttributeRules(): self
373
    {
374 5
        $this->attributeRules    = null;
0 ignored issues
show
Documentation Bug introduced by
It seems like null of type null is incompatible with the declared type array<integer,integer> of property $attributeRules.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
375 5
        $this->attributeRulesIdx = null;
0 ignored issues
show
Documentation Bug introduced by
It seems like null of type null is incompatible with the declared type array of property $attributeRulesIdx.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
376
377 5
        return $this;
378
    }
379
380
    /**
381
     * @return int[]|null
382
     */
383 4
    private function getAttributeRules(): ?array
384
    {
385 4
        return $this->attributeRules;
386
    }
387
388
    /**
389
     * @param string $name
390
     *
391
     * @return int|null
392
     *
393
     * @SuppressWarnings(PHPMD.StaticAccess)
394
     */
395 3
    private function getAttributeIndex(string $name): ?int
396
    {
397 3
        $index = $this->attributeRulesIdx[$name] ?? null;
398
399 3
        return $index;
400
    }
401
402
    /**
403
     * @param array $indexes
404
     *
405
     * @return bool
406
     *
407
     * @SuppressWarnings(PHPMD.StaticAccess)
408
     */
409 3
    private function executeStarts(array $indexes): bool
410
    {
411 3
        return BlockInterpreter::executeStarts(
412 3
            $indexes,
413 3
            $this->getBlocks(),
414 3
            $this->getContextStorage(),
415 3
            $this->getErrorAggregator()
416
        );
417
    }
418
419
    /**
420
     * @param array $indexes
421
     *
422
     * @return bool
423
     *
424
     * @SuppressWarnings(PHPMD.StaticAccess)
425
     */
426 3
    private function executeEnds(array $indexes): bool
427
    {
428 3
        return BlockInterpreter::executeEnds(
429 3
            $indexes,
430 3
            $this->getBlocks(),
431 3
            $this->getContextStorage(),
432 3
            $this->getErrorAggregator()
433
        );
434
    }
435
436
    /**
437
     * @param mixed $input
438
     * @param int   $index
439
     *
440
     * @return bool
441
     *
442
     * @SuppressWarnings(PHPMD.StaticAccess)
443
     */
444 2
    private function executeBlock($input, int $index): bool
445
    {
446 2
        return BlockInterpreter::executeBlock(
447 2
            $input,
448 2
            $index,
449 2
            $this->getBlocks(),
450 2
            $this->getContextStorage(),
451 2
            $this->getCaptureAggregator(),
452 2
            $this->getErrorAggregator()
453
        );
454
    }
455
456
    /**
457
     * @param array $rules
458
     *
459
     * @return bool
460
     *
461
     * @SuppressWarnings(PHPMD.StaticAccess)
462
     */
463 5
    private function debugCheckIndexesExist(array $rules): bool
464
    {
465 5
        $allOk = true;
466
467 5
        $indexes = array_merge(
468 5
            AttributeRulesSerializer::getRulesIndexes($rules),
469 5
            AttributeRulesSerializer::getRulesStartIndexes($rules),
470 5
            AttributeRulesSerializer::getRulesEndIndexes($rules)
471
        );
472
473 5
        foreach ($indexes as $index) {
474 4
            $allOk = $allOk && is_int($index) && AttributeRulesSerializer::isRuleExist($index, $this->getBlocks());
475
        }
476
477 5
        return $allOk;
478
    }
479
}
480