Completed
Push — develop ( 20327f...69b019 )
by Neomerx
10:22 queued 02:22
created

QueryValidator::executeEnds()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 9
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 1

Importance

Changes 0
Metric Value
dl 0
loc 9
c 0
b 0
f 0
ccs 6
cts 6
cp 1
rs 9.6666
cc 1
eloc 6
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\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()));
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...
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
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...
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
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...
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;
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...
373 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...
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