Completed
Push — master ( 48c4f2...a2b4be )
by Neomerx
01:38
created

Validator::assert()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 8
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 8
ccs 4
cts 4
cp 1
rs 9.4285
c 0
b 0
f 0
cc 2
eloc 4
nc 2
nop 1
crap 2
1
<?php namespace Limoncello\Flute\Validation;
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\Contracts\L10n\FormatterFactoryInterface;
21
use Limoncello\Contracts\L10n\FormatterInterface;
22
use Limoncello\Flute\Contracts\Validation\ErrorCodes;
23
use Limoncello\Flute\Contracts\Validation\JsonApiValidatorInterface;
24
use Limoncello\Flute\Http\JsonApiResponse;
25
use Limoncello\Flute\Validation\Execution\JsonApiErrorCollection;
26
use Limoncello\Flute\Validation\Execution\JsonApiRuleSerializer;
27
use Limoncello\Flute\Validation\Rules\RelationshipsTrait;
28
use Limoncello\Validation\Contracts\Errors\ErrorInterface;
29
use Limoncello\Validation\Contracts\Execution\ContextStorageInterface;
30
use Limoncello\Validation\Execution\BlockInterpreter;
31
use Limoncello\Validation\Execution\ContextStorage;
32
use Limoncello\Validation\Validator\BaseValidator;
33
use Neomerx\JsonApi\Contracts\Document\DocumentInterface as DI;
34
use Neomerx\JsonApi\Exceptions\JsonApiException;
35
use Psr\Container\ContainerInterface;
36
37
/**
38
 * @package Limoncello\Flute
39
 *
40
 * @SuppressWarnings(PHPMD.CouplingBetweenObjects)
41
 * @SuppressWarnings(PHPMD.ExcessiveClassComplexity)
42
 */
43
class Validator extends BaseValidator implements JsonApiValidatorInterface
44
{
45
    use RelationshipsTrait, HasContainerTrait;
46
47
    /**
48
     * Namespace for string resources.
49
     */
50
    const RESOURCES_NAMESPACE = 'Limoncello.Flute.Validation';
51
52
    /** Rule description index */
53
    const RULE_INDEX = 0;
54
55
    /** Rule description index */
56
    const RULE_ATTRIBUTES = self::RULE_INDEX + 1;
57
58
    /** Rule description index */
59
    const RULE_TO_ONE = self::RULE_ATTRIBUTES + 1;
60
61
    /** Rule description index */
62
    const RULE_TO_MANY = self::RULE_TO_ONE + 1;
63
64
    /** Rule description index */
65
    const RULE_UNLISTED_ATTRIBUTE = self::RULE_TO_MANY + 1;
66
67
    /** Rule description index */
68
    const RULE_UNLISTED_RELATIONSHIP = self::RULE_UNLISTED_ATTRIBUTE + 1;
69
70
    /**
71
     * @var int
72
     */
73
    private $errorStatus;
74
75
    /**
76
     * @var ContextStorageInterface
77
     */
78
    private $contextStorage;
79
80
    /**
81
     * @var JsonApiErrorCollection
82
     */
83
    private $jsonApiErrors;
84
85
    /**
86
     * @var array
87
     */
88
    private $blocks;
89
90
    /**
91
     * @var array
92
     */
93
    private $idRule;
94
95
    /**
96
     * @var array
97
     */
98
    private $typeRule;
99
100
    /**
101
     * @var int[]
102
     */
103
    private $attributeRules;
104
105
    /**
106
     * @var int[]
107
     */
108
    private $toOneRules;
109
110
    /**
111
     * @var int[]
112
     */
113
    private $toManyRules;
114
115
    /**
116
     * @var bool
117
     */
118
    private $isIgnoreUnknowns;
119
120
    /**
121
     * @var FormatterInterface|null
122
     */
123
    private $messageFormatter;
124
125
    /**
126
     * @param string             $name
127
     * @param array              $data
128
     * @param ContainerInterface $container
129
     * @param int                $errorStatus
130
     *
131
     * @SuppressWarnings(PHPMD.StaticAccess)
132
     */
133 20
    public function __construct(
134
        string $name,
135
        array $data,
136
        ContainerInterface $container,
137
        int $errorStatus = JsonApiResponse::HTTP_UNPROCESSABLE_ENTITY
138
    ) {
139 20
        $this->setContainer($container);
140
141 20
        $ruleSet           = JsonApiRuleSerializer::extractRuleSet($name, $data);
142 20
        $this->blocks      = JsonApiRuleSerializer::extractBlocks($data);
143 20
        $this->idRule      = JsonApiRuleSerializer::getIdRule($ruleSet);
144 20
        $this->typeRule    = JsonApiRuleSerializer::getTypeRule($ruleSet);
145 20
        $this->errorStatus = $errorStatus;
146
147
        $this
148 20
            ->setAttributeRules(JsonApiRuleSerializer::getAttributeRules($ruleSet))
149 20
            ->setToOneIndexes(JsonApiRuleSerializer::getToOneRules($ruleSet))
150 20
            ->setToManyIndexes(JsonApiRuleSerializer::getToManyRules($ruleSet))
151 20
            ->disableIgnoreUnknowns();
152
153 20
        parent::__construct();
154
    }
155
156
    /**
157
     * @inheritdoc
158
     */
159 13
    public function assert($jsonData): JsonApiValidatorInterface
160
    {
161 13
        if ($this->validate($jsonData) === false) {
162 5
            throw new JsonApiException($this->getJsonApiErrorCollection(), $this->getErrorStatus());
163
        }
164
165 3
        return $this;
166
    }
167
168
    /**
169
     * @inheritdoc
170
     *
171
     * @SuppressWarnings(PHPMD.ElseExpression)
172
     */
173 19
    public function validate($input): bool
174
    {
175 19
        $this->reInitAggregatorsIfNeeded();
176
177 19
        if (is_array($input) === true) {
178
            $this
179 18
                ->validateType($input)
180 18
                ->validateId($input)
181 13
                ->validateAttributes($input)
182 13
                ->validateRelationships($input);
183
        } else {
184 1
            $title   = $this->formatMessage(ErrorCodes::INVALID_VALUE);
185 1
            $details = $this->formatMessage(ErrorCodes::INVALID_VALUE);
186 1
            $this->getJsonApiErrorCollection()->addDataError($title, $details, $this->getErrorStatus());
187
        }
188
189 13
        $hasNoErrors = $this->getJsonApiErrorCollection()->count() <= 0;
190
191 13
        return $hasNoErrors;
192
    }
193
194
    /**
195
     * @inheritdoc
196
     */
197 6
    public function getJsonApiErrors(): array
198
    {
199 6
        return $this->getJsonApiErrorCollection()->getArrayCopy();
200
    }
201
202
    /**
203
     * @inheritdoc
204
     */
205 8
    public function getJsonApiCaptures(): array
206
    {
207 8
        return $this->getCaptures();
208
    }
209
210
    /**
211
     * @return BaseValidator
0 ignored issues
show
Documentation introduced by
Consider making the return type a bit more specific; maybe use Validator.

This check looks for the generic type array as a return type and suggests a more specific type. This type is inferred from the actual code.

Loading history...
212
     */
213 20
    protected function resetAggregators(): BaseValidator
214
    {
215 20
        $self = parent::resetAggregators();
216
217 20
        $this->jsonApiErrors  = $this->createJsonApiErrors();
218 20
        $this->contextStorage = $this->createContextStorage();
219
220 20
        return $self;
221
    }
222
223
    /**
224
     * @param array $jsonData
225
     *
226
     * @return self
227
     *
228
     * @SuppressWarnings(PHPMD.StaticAccess)
229
     * @SuppressWarnings(PHPMD.ElseExpression)
230
     */
231 18
    private function validateType(array $jsonData): self
232
    {
233
        // execute start(s)
234 18
        $starts = JsonApiRuleSerializer::getRuleStartIndexes($this->getTypeRule());
235 18
        $this->executeStarts($starts);
236
237 18
        if (array_key_exists(DI::KEYWORD_DATA, $jsonData) === true &&
238 18
            array_key_exists(DI::KEYWORD_TYPE, $data = $jsonData[DI::KEYWORD_DATA]) === true
239
        ) {
240
            // execute main validation block(s)
241 17
            $index = JsonApiRuleSerializer::getRuleIndex($this->getTypeRule());
242 17
            $this->executeBlock($data[DI::KEYWORD_TYPE], $index);
243
        } else {
244 1
            $title   = $this->formatMessage(ErrorCodes::INVALID_VALUE);
245 1
            $details = $this->formatMessage(ErrorCodes::TYPE_MISSING);
246 1
            $this->getJsonApiErrorCollection()->addDataTypeError($title, $details, $this->getErrorStatus());
247
        }
248
249
        // execute end(s)
250 18
        $ends = JsonApiRuleSerializer::getRuleEndIndexes($this->getTypeRule());
251 18
        $this->executeEnds($ends);
252
253 18
        if (count($this->getErrorAggregator()) > 0) {
0 ignored issues
show
Bug introduced by
The method getErrorAggregator() does not seem to exist on object<Limoncello\Flute\Validation\Validator>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
254 1
            $title = $this->formatMessage(ErrorCodes::INVALID_VALUE);
255 1
            foreach ($this->getErrorAggregator()->get() as $error) {
0 ignored issues
show
Bug introduced by
The method getErrorAggregator() does not seem to exist on object<Limoncello\Flute\Validation\Validator>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
256 1
                $this->getJsonApiErrorCollection()
257 1
                    ->addDataTypeError($title, $this->getMessage($error), $this->getErrorStatus());
258
            }
259 1
            $this->getErrorAggregator()->clear();
0 ignored issues
show
Bug introduced by
The method getErrorAggregator() does not seem to exist on object<Limoncello\Flute\Validation\Validator>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
260
        }
261
262 18
        return $this;
263
    }
264
265
    /**
266
     * @param array $jsonData
267
     *
268
     * @return self
269
     *
270
     * @SuppressWarnings(PHPMD.StaticAccess)
271
     */
272 18
    private function validateId(array $jsonData): self
273
    {
274
        // execute start(s)
275 18
        $starts = JsonApiRuleSerializer::getRuleStartIndexes($this->getIdRule());
276 18
        $this->executeStarts($starts);
277
278
        // execute main validation block(s)
279 18
        if (array_key_exists(DI::KEYWORD_DATA, $jsonData) === true &&
280 18
            array_key_exists(DI::KEYWORD_ID, $data = $jsonData[DI::KEYWORD_DATA]) === true
281
        ) {
282 16
            $index = JsonApiRuleSerializer::getRuleIndex($this->getIdRule());
283 16
            $this->executeBlock($data[DI::KEYWORD_ID], $index);
284
        }
285
286
        // execute end(s)
287 13
        $ends = JsonApiRuleSerializer::getRuleEndIndexes($this->getIdRule());
288 13
        $this->executeEnds($ends);
289
290 13
        if (count($this->getErrorAggregator()) > 0) {
0 ignored issues
show
Bug introduced by
The method getErrorAggregator() does not seem to exist on object<Limoncello\Flute\Validation\Validator>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
291 1
            $title = $this->formatMessage(ErrorCodes::INVALID_VALUE);
292 1
            foreach ($this->getErrorAggregator()->get() as $error) {
0 ignored issues
show
Bug introduced by
The method getErrorAggregator() does not seem to exist on object<Limoncello\Flute\Validation\Validator>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
293 1
                $this->getJsonApiErrorCollection()
294 1
                    ->addDataIdError($title, $this->getMessage($error), $this->getErrorStatus());
295
            }
296 1
            $this->getErrorAggregator()->clear();
0 ignored issues
show
Bug introduced by
The method getErrorAggregator() does not seem to exist on object<Limoncello\Flute\Validation\Validator>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
297
        }
298
299 13
        return $this;
300
    }
301
302
    /**
303
     * @param array $jsonData
304
     *
305
     * @return self
306
     *
307
     * @SuppressWarnings(PHPMD.StaticAccess)
308
     * @SuppressWarnings(PHPMD.ElseExpression)
309
     */
310 13
    private function validateAttributes(array $jsonData): self
311
    {
312
        // execute start(s)
313 13
        $starts = JsonApiRuleSerializer::getRulesStartIndexes($this->getAttributeRules());
314 13
        $this->executeStarts($starts);
315
316 13
        if (array_key_exists(DI::KEYWORD_DATA, $jsonData) === true &&
317 13
            array_key_exists(DI::KEYWORD_ATTRIBUTES, $data = $jsonData[DI::KEYWORD_DATA]) === true
318
        ) {
319 12
            if (is_array($attributes = $data[DI::KEYWORD_ATTRIBUTES]) === false) {
320 2
                $title   = $this->formatMessage(ErrorCodes::INVALID_VALUE);
321 2
                $details = $this->formatMessage(ErrorCodes::INVALID_ATTRIBUTES);
322 2
                $this->getJsonApiErrorCollection()->addAttributesError($title, $details, $this->getErrorStatus());
323
            } else {
324
                // execute main validation block(s)
325 10
                foreach ($attributes as $name => $value) {
326 8
                    if (($index = $this->getAttributeIndex($name)) !== null) {
327 7
                        $this->executeBlock($value, $index);
328 1
                    } elseif ($this->isIgnoreUnknowns() === false) {
329 1
                        $title   = $this->formatMessage(ErrorCodes::INVALID_VALUE);
330 1
                        $details = $this->formatMessage(ErrorCodes::UNKNOWN_ATTRIBUTE);
331 1
                        $status  = $this->getErrorStatus();
332 8
                        $this->getJsonApiErrorCollection()->addDataAttributeError($name, $title, $details, $status);
333
                    }
334
                }
335
            }
336
        }
337
338
        // execute end(s)
339 13
        $ends = JsonApiRuleSerializer::getRulesEndIndexes($this->getAttributeRules());
340 13
        $this->executeEnds($ends);
341
342 13
        if (count($this->getErrorAggregator()) > 0) {
0 ignored issues
show
Bug introduced by
The method getErrorAggregator() does not seem to exist on object<Limoncello\Flute\Validation\Validator>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
343 2
            foreach ($this->getErrorAggregator()->get() as $error) {
0 ignored issues
show
Bug introduced by
The method getErrorAggregator() does not seem to exist on object<Limoncello\Flute\Validation\Validator>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
344 2
                $this->getJsonApiErrorCollection()->addValidationAttributeError($error);
345
            }
346 2
            $this->getErrorAggregator()->clear();
0 ignored issues
show
Bug introduced by
The method getErrorAggregator() does not seem to exist on object<Limoncello\Flute\Validation\Validator>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
347
        }
348
349 13
        return $this;
350
    }
351
352
    /**
353
     * @param array $jsonData
354
     *
355
     * @return self
356
     *
357
     * @SuppressWarnings(PHPMD.StaticAccess)
358
     * @SuppressWarnings(PHPMD.ElseExpression)
359
     */
360 13
    private function validateRelationships(array $jsonData): self
361
    {
362
        // execute start(s)
363 13
        $starts = array_merge(
364 13
            JsonApiRuleSerializer::getRulesStartIndexes($this->getToOneRules()),
365 13
            JsonApiRuleSerializer::getRulesStartIndexes($this->getToManyRules())
366
        );
367 13
        $this->executeStarts($starts);
368
369 13
        if (array_key_exists(DI::KEYWORD_DATA, $jsonData) === true &&
370 13
            array_key_exists(DI::KEYWORD_RELATIONSHIPS, $data = $jsonData[DI::KEYWORD_DATA]) === true
371
        ) {
372 9
            if (is_array($relationships = $data[DI::KEYWORD_RELATIONSHIPS]) === false) {
373 1
                $title   = $this->formatMessage(ErrorCodes::INVALID_VALUE);
374 1
                $details = $this->formatMessage(ErrorCodes::INVALID_RELATIONSHIP_TYPE);
375 1
                $this->getJsonApiErrorCollection()->addRelationshipsError($title, $details, $this->getErrorStatus());
376
            } else {
377
                // ok we got to something that could be null or a valid relationship
378 8
                $toOneIndexes  = JsonApiRuleSerializer::getRulesIndexes($this->getToOneRules());
379 8
                $toManyIndexes = JsonApiRuleSerializer::getRulesIndexes($this->getToManyRules());
380
381 8
                foreach ($relationships as $name => $relationship) {
382 8
                    if (array_key_exists($name, $toOneIndexes) === true) {
383
                        // it might be to1 relationship
384 7
                        $this->validateAsToOneRelationship($toOneIndexes[$name], $name, $relationship);
385 7
                    } elseif (array_key_exists($name, $toManyIndexes) === true) {
386
                        // it might be toMany relationship
387 6
                        $this->validateAsToManyRelationship($toManyIndexes[$name], $name, $relationship);
388
                    } else {
389
                        // unknown relationship
390 1
                        $title   = $this->formatMessage(ErrorCodes::INVALID_VALUE);
391 1
                        $details = $this->formatMessage(ErrorCodes::UNKNOWN_RELATIONSHIP);
392 1
                        $status  = $this->getErrorStatus();
393 7
                        $this->getJsonApiErrorCollection()->addRelationshipError($name, $title, $details, $status);
394
                    }
395
                }
396
            }
397
        }
398
399
        // execute end(s)
400 12
        $ends = array_merge(
401 12
            JsonApiRuleSerializer::getRulesEndIndexes($this->getToOneRules()),
402 12
            JsonApiRuleSerializer::getRulesEndIndexes($this->getToManyRules())
403
        );
404 12
        $this->executeEnds($ends);
405
406 12
        if (count($this->getErrorAggregator()) > 0) {
0 ignored issues
show
Bug introduced by
The method getErrorAggregator() does not seem to exist on object<Limoncello\Flute\Validation\Validator>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
407 3
            foreach ($this->getErrorAggregator()->get() as $error) {
0 ignored issues
show
Bug introduced by
The method getErrorAggregator() does not seem to exist on object<Limoncello\Flute\Validation\Validator>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
408 3
                $this->getJsonApiErrorCollection()->addValidationRelationshipError($error);
409
            }
410 3
            $this->getErrorAggregator()->clear();
0 ignored issues
show
Bug introduced by
The method getErrorAggregator() does not seem to exist on object<Limoncello\Flute\Validation\Validator>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
411
        }
412
413 12
        return $this;
414
    }
415
416
    /**
417
     * @param int    $index
418
     * @param string $name
419
     * @param mixed  $mightBeRelationship
420
     *
421
     * @return void
422
     *
423
     * @SuppressWarnings(PHPMD.ElseExpression)
424
     */
425 7
    private function validateAsToOneRelationship(int $index, string $name, $mightBeRelationship): void
426
    {
427 7
        if (is_array($mightBeRelationship) === true &&
428 7
            array_key_exists(DI::KEYWORD_DATA, $mightBeRelationship) === true &&
429 7
            ($parsed = $this->parseSingleRelationship($mightBeRelationship[DI::KEYWORD_DATA])) !== false
430
        ) {
431
            // All right we got something. Now pass it to a validation rule.
432 5
            $this->executeBlock($parsed, $index);
433
        } else {
434 2
            $title   = $this->formatMessage(ErrorCodes::INVALID_VALUE);
435 2
            $details = $this->formatMessage(ErrorCodes::INVALID_RELATIONSHIP);
436 2
            $this->getJsonApiErrorCollection()->addRelationshipError($name, $title, $details, $this->getErrorStatus());
437
        }
438
    }
439
440
    /**
441
     * @param int    $index
442
     * @param string $name
443
     * @param mixed  $mightBeRelationship
444
     *
445
     * @return void
446
     *
447
     * @SuppressWarnings(PHPMD.ElseExpression)
448
     */
449 6
    private function validateAsToManyRelationship(int $index, string $name, $mightBeRelationship): void
450
    {
451 6
        $isParsed       = true;
452 6
        $collectedPairs = [];
453 6
        if (is_array($mightBeRelationship) === true &&
454 6
            array_key_exists(DI::KEYWORD_DATA, $mightBeRelationship) === true &&
455 6
            is_array($data = $mightBeRelationship[DI::KEYWORD_DATA]) === true
456
        ) {
457 4
            foreach ($data as $mightTypeAndId) {
458
                // we accept only pairs of type and id (no `null`s are accepted).
459 3
                if (is_array($parsed = $this->parseSingleRelationship($mightTypeAndId)) === true) {
460 2
                    $collectedPairs[] = $parsed;
461
                } else {
462 1
                    $isParsed = false;
463 4
                    break;
464
                }
465
            }
466
        } else {
467 2
            $isParsed = false;
468
        }
469
470 6
        if ($isParsed === true) {
471
            // All right we got something. Now pass it to a validation rule.
472 3
            $this->executeBlock($collectedPairs, $index);
473
        } else {
474 3
            $title   = $this->formatMessage(ErrorCodes::INVALID_VALUE);
475 3
            $details = $this->formatMessage(ErrorCodes::INVALID_RELATIONSHIP);
476 3
            $this->getJsonApiErrorCollection()->addRelationshipError($name, $title, $details, $this->getErrorStatus());
477
        }
478
    }
479
480
    /**
481
     * @param mixed $data
482
     *
483
     * @return array|null|false Either `array` ($type => $id), or `null`, or `false` on error.
0 ignored issues
show
Documentation introduced by
Consider making the return type a bit more specific; maybe use array<*,integer|double|string|boolean>|false|null.

This check looks for the generic type array as a return type and suggests a more specific type. This type is inferred from the actual code.

Loading history...
484
     *
485
     * @SuppressWarnings(PHPMD.ElseExpression)
486
     */
487 6
    private function parseSingleRelationship($data)
488
    {
489 6
        if ($data === null) {
490 2
            $result = null;
491 4
        } elseif (is_array($data) === true &&
492 4
            array_key_exists(DI::KEYWORD_TYPE, $data) === true &&
493 4
            array_key_exists(DI::KEYWORD_ID, $data) === true &&
494 4
            is_scalar($type = $data[DI::KEYWORD_TYPE]) === true &&
495 4
            is_scalar($index = $data[DI::KEYWORD_ID]) === true
496
        ) {
497 3
            $result = [$type => $index];
498
        } else {
499 1
            $result = false;
500
        }
501
502 6
        return $result;
503
    }
504
505
    /**
506
     * Re-initializes internal aggregators for captures, errors, etc.
507
     */
508 19
    private function reInitAggregatorsIfNeeded(): void
509
    {
510 19
        $this->areAggregatorsDirty() === false ?: $this->resetAggregators();
511
    }
512
513
    /**
514
     * @param mixed $input
515
     * @param int   $index
516
     *
517
     * @return void
518
     *
519
     * @SuppressWarnings(PHPMD.StaticAccess)
520
     */
521 17
    private function executeBlock($input, int $index): void
522
    {
523 17
        BlockInterpreter::executeBlock(
524 17
            $input,
525 17
            $index,
526 17
            $this->getBlocks(),
527 17
            $this->getContextStorage(),
528 17
            $this->getCaptureAggregator(),
0 ignored issues
show
Bug introduced by
The method getCaptureAggregator() does not seem to exist on object<Limoncello\Flute\Validation\Validator>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
529 17
            $this->getErrorAggregator()
0 ignored issues
show
Bug introduced by
The method getErrorAggregator() does not seem to exist on object<Limoncello\Flute\Validation\Validator>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
530
        );
531
    }
532
533
    /**
534
     * @param array $indexes
535
     *
536
     * @return void
537
     *
538
     * @SuppressWarnings(PHPMD.StaticAccess)
539
     */
540 18
    private function executeStarts(array $indexes): void
541
    {
542 18
        BlockInterpreter::executeStarts(
543 18
            $indexes,
544 18
            $this->getBlocks(),
545 18
            $this->getContextStorage(),
546 18
            $this->getErrorAggregator()
0 ignored issues
show
Bug introduced by
The method getErrorAggregator() does not seem to exist on object<Limoncello\Flute\Validation\Validator>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
547
        );
548
    }
549
550
    /**
551
     * @param array $indexes
552
     *
553
     * @return void
554
     *
555
     * @SuppressWarnings(PHPMD.StaticAccess)
556
     */
557 18
    private function executeEnds(array $indexes): void
558
    {
559 18
        BlockInterpreter::executeEnds(
560 18
            $indexes,
561 18
            $this->getBlocks(),
562 18
            $this->getContextStorage(),
563 18
            $this->getErrorAggregator()
0 ignored issues
show
Bug introduced by
The method getErrorAggregator() does not seem to exist on object<Limoncello\Flute\Validation\Validator>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
564
        );
565
    }
566
567
    /**
568
     * @param ErrorInterface $error
569
     *
570
     * @return string
571
     */
572 1
    private function getMessage(ErrorInterface $error): string
573
    {
574 1
        $context = $error->getMessageContext();
575 1
        $args    = $context === null ? [] : $context;
576 1
        $message = $this->formatMessage($error->getMessageCode(), $args);
577
578 1
        return $message;
579
    }
580
581
    /**
582
     * @return array
583
     */
584 18
    protected function getIdRule(): array
585
    {
586 18
        return $this->idRule;
587
    }
588
589
    /**
590
     * @return array
591
     */
592 18
    protected function getTypeRule(): array
593
    {
594 18
        return $this->typeRule;
595
    }
596
597
    /**
598
     * @return ContextStorageInterface
599
     */
600 18
    protected function getContextStorage(): ContextStorageInterface
601
    {
602 18
        return $this->contextStorage;
603
    }
604
605
    /**
606
     * @return ContextStorageInterface
607
     */
608 20
    protected function createContextStorage(): ContextStorageInterface
609
    {
610 20
        return new ContextStorage($this->getBlocks(), $this->getContainer());
0 ignored issues
show
Unused Code introduced by
The call to ContextStorage::__construct() has too many arguments starting with $this->getContainer().

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
611
    }
612
613
    /**
614
     * @return JsonApiErrorCollection
615
     */
616 13
    protected function getJsonApiErrorCollection(): JsonApiErrorCollection
617
    {
618 13
        return $this->jsonApiErrors;
619
    }
620
621
    /**
622
     * @return JsonApiErrorCollection
623
     */
624 20
    protected function createJsonApiErrors(): JsonApiErrorCollection
625
    {
626 20
        return new JsonApiErrorCollection($this->getContainer(), $this->getErrorStatus());
627
    }
628
629
    /**
630
     * @return int
631
     */
632 20
    protected function getErrorStatus(): int
633
    {
634 20
        return $this->errorStatus;
635
    }
636
637
    /**
638
     * @return bool
639
     */
640 1
    protected function isIgnoreUnknowns(): bool
641
    {
642 1
        return $this->isIgnoreUnknowns;
643
    }
644
645
    /**
646
     * @return self
647
     */
648 1
    protected function enableIgnoreUnknowns(): self
649
    {
650 1
        $this->isIgnoreUnknowns = true;
651
652 1
        return $this;
653
    }
654
655
    /**
656
     * @return self
657
     */
658 20
    protected function disableIgnoreUnknowns(): self
659
    {
660 20
        $this->isIgnoreUnknowns = false;
661
662 20
        return $this;
663
    }
664
665
    /**
666
     * @param array $rules
667
     *
668
     * @return self
669
     */
670 20
    private function setAttributeRules(array $rules): self
671
    {
672 20
        assert($this->debugCheckIndexesExist($rules));
673
674 20
        $this->attributeRules = $rules;
675
676 20
        return $this;
677
    }
678
679
    /**
680
     * @param array $rules
681
     *
682
     * @return self
683
     */
684 20
    private function setToOneIndexes(array $rules): self
685
    {
686 20
        assert($this->debugCheckIndexesExist($rules));
687
688 20
        $this->toOneRules = $rules;
689
690 20
        return $this;
691
    }
692
693
    /**
694
     * @param array $rules
695
     *
696
     * @return self
697
     */
698 20
    private function setToManyIndexes(array $rules): self
699
    {
700 20
        assert($this->debugCheckIndexesExist($rules));
701
702 20
        $this->toManyRules = $rules;
703
704 20
        return $this;
705
    }
706
707
    /**
708
     * @return int[]
709
     */
710 13
    protected function getAttributeRules(): array
711
    {
712 13
        return $this->attributeRules;
713
    }
714
715
    /**
716
     * @return int[]
717
     */
718 13
    protected function getToOneRules(): array
719
    {
720 13
        return $this->toOneRules;
721
    }
722
723
    /**
724
     * @return int[]
725
     */
726 13
    protected function getToManyRules(): array
727
    {
728 13
        return $this->toManyRules;
729
    }
730
731
    /**
732
     * @return array
733
     */
734 20
    private function getBlocks(): array
735
    {
736 20
        return $this->blocks;
737
    }
738
739
    /**
740
     * @return FormatterInterface
741
     */
742 7
    protected function getMessageFormatter(): FormatterInterface
743
    {
744 7
        if ($this->messageFormatter === null) {
745
            /** @var FormatterFactoryInterface $factory */
746 7
            $factory                = $this->getContainer()->get(FormatterFactoryInterface::class);
747 7
            $this->messageFormatter = $factory->createFormatter(static::RESOURCES_NAMESPACE);
748
        }
749
750 7
        return $this->messageFormatter;
751
    }
752
753
    /**
754
     * @param string $name
755
     *
756
     * @return int|null
757
     *
758
     * @SuppressWarnings(PHPMD.StaticAccess)
759
     */
760 8
    private function getAttributeIndex(string $name): ?int
761
    {
762 8
        $indexes = JsonApiRuleSerializer::getRulesIndexes($this->getAttributeRules());
763 8
        $index   = $indexes[$name] ?? null;
764
765 8
        return $index;
766
    }
767
768
    /**
769
     * @param int   $messageId
770
     * @param array $args
771
     *
772
     * @return string
773
     */
774 7
    private function formatMessage(int $messageId, array $args = []): string
775
    {
776 7
        $message = $this->getMessageFormatter()->formatMessage($messageId, $args);
777
778 7
        return $message;
779
    }
780
781
    /**
782
     * @param array $rules
783
     *
784
     * @return bool
785
     *
786
     * @SuppressWarnings(PHPMD.StaticAccess)
787
     */
788 20
    private function debugCheckIndexesExist(array $rules): bool
789
    {
790 20
        $allOk = true;
791
792 20
        $indexes = array_merge(
793 20
            JsonApiRuleSerializer::getRulesIndexes($rules),
794 20
            JsonApiRuleSerializer::getRulesStartIndexes($rules),
795 20
            JsonApiRuleSerializer::getRulesEndIndexes($rules)
796
        );
797
798 20
        foreach ($indexes as $index) {
799 16
            $allOk = $allOk && is_int($index) && JsonApiRuleSerializer::isRuleExist($index, $this->getBlocks());
800
        }
801
802 20
        return $allOk;
803
    }
804
}
805