Completed
Push — develop ( 90d90f...48c4f2 )
by Neomerx
06:19 queued 04:30
created

Validator::hasAttributeIndex()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 7
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 1

Importance

Changes 0
Metric Value
dl 0
loc 7
ccs 4
cts 4
cp 1
rs 9.4285
c 0
b 0
f 0
cc 1
eloc 4
nc 1
nop 1
crap 1
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\ContextStorage;
26
use Limoncello\Flute\Validation\Execution\JsonApiErrorCollection;
27
use Limoncello\Flute\Validation\Execution\JsonApiRuleSerializer;
28
use Limoncello\Flute\Validation\Rules\RelationshipsTrait;
29
use Limoncello\Validation\Contracts\Errors\ErrorInterface;
30
use Limoncello\Validation\Contracts\Execution\ContextStorageInterface;
31
use Limoncello\Validation\Execution\BlockInterpreter;
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 9
    public function __construct(
134
        string $name,
135
        array $data,
136
        ContainerInterface $container,
137
        int $errorStatus = JsonApiResponse::HTTP_UNPROCESSABLE_ENTITY
138
    ) {
139 9
        $this->setContainer($container);
140
141 9
        $ruleSet           = JsonApiRuleSerializer::extractRuleSet($name, $data);
142 9
        $this->blocks      = JsonApiRuleSerializer::extractBlocks($data);
143 9
        $this->idRule      = JsonApiRuleSerializer::getIdRule($ruleSet);
144 9
        $this->typeRule    = JsonApiRuleSerializer::getTypeRule($ruleSet);
145 9
        $this->errorStatus = $errorStatus;
146
147
        $this
148 9
            ->setAttributeRules(JsonApiRuleSerializer::getAttributeRules($ruleSet))
149 9
            ->setToOneIndexes(JsonApiRuleSerializer::getToOneRules($ruleSet))
150 9
            ->setToManyIndexes(JsonApiRuleSerializer::getToManyRules($ruleSet))
151 9
            ->disableIgnoreUnknowns();
152
153 9
        parent::__construct();
154
    }
155
156
    /**
157
     * @inheritdoc
158
     */
159 7
    public function assert($jsonData): JsonApiValidatorInterface
160
    {
161 7
        if ($this->validate($jsonData) === false) {
162 1
            throw new JsonApiException($this->getJsonApiErrorCollection(), $this->getErrorStatus());
163
        }
164
165
        return $this;
166
    }
167
168
    /**
169
     * @inheritdoc
170
     *
171
     * @SuppressWarnings(PHPMD.ElseExpression)
172
     */
173 8
    public function validate($input): bool
174
    {
175 8
        $this->reInitAggregatorsIfNeeded();
176
177 8
        if (is_array($input) === true) {
178
            $this
179 7
                ->validateType($input)
180
                ->validateId($input)
181
                ->validateAttributes($input)
182
                ->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 1
        $hasNoErrors = $this->getJsonApiErrorCollection()->count() <= 0;
190
191 1
        return $hasNoErrors;
192
    }
193
194
    /**
195
     * @inheritdoc
196
     */
197
    public function getJsonApiErrors(): array
198
    {
199
        return $this->getJsonApiErrorCollection()->getArrayCopy();
200
    }
201
202
    /**
203
     * @inheritdoc
204
     */
205
    public function getJsonApiCaptures(): array
206
    {
207
        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 9
    protected function resetAggregators(): BaseValidator
214
    {
215 9
        $self = parent::resetAggregators();
216
217 9
        $this->jsonApiErrors  = $this->createJsonApiErrors();
218 9
        $this->contextStorage = $this->createContextStorage();
219
220 9
        return $self;
221
    }
222
223
    /**
224
     * @param array $jsonData
225
     *
226
     * @return self
227
     *
228
     * @SuppressWarnings(PHPMD.StaticAccess)
229
     * @SuppressWarnings(PHPMD.ElseExpression)
230
     */
231 7
    private function validateType(array $jsonData): self
232
    {
233
        // execute start(s)
234 7
        $starts = JsonApiRuleSerializer::getRuleStartIndexes($this->getTypeRule());
235 7
        $this->executeStarts($starts);
236
237
        if (array_key_exists(DI::KEYWORD_DATA, $jsonData) === true &&
238
            array_key_exists(DI::KEYWORD_TYPE, $data = $jsonData[DI::KEYWORD_DATA]) === true
239
        ) {
240
            // execute main validation block(s)
241
            $index = JsonApiRuleSerializer::getRuleIndex($this->getTypeRule());
242
            $this->executeBlock($data[DI::KEYWORD_TYPE], $index);
243
        } else {
244
            $title   = $this->formatMessage(ErrorCodes::INVALID_VALUE);
245
            $details = $this->formatMessage(ErrorCodes::TYPE_MISSING);
246
            $this->getJsonApiErrorCollection()->addDataTypeError($title, $details, $this->getErrorStatus());
247
        }
248
249
        // execute end(s)
250
        $ends = JsonApiRuleSerializer::getRuleEndIndexes($this->getTypeRule());
251
        $this->executeEnds($ends);
252
253
        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
            $title = $this->formatMessage(ErrorCodes::INVALID_VALUE);
255
            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
                $this->getJsonApiErrorCollection()
257
                    ->addDataTypeError($title, $this->getMessage($error), $this->getErrorStatus());
258
            }
259
            $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
        return $this;
263
    }
264
265
    /**
266
     * @param array $jsonData
267
     *
268
     * @return self
269
     *
270
     * @SuppressWarnings(PHPMD.StaticAccess)
271
     */
272
    private function validateId(array $jsonData): self
273
    {
274
        // execute start(s)
275
        $starts = JsonApiRuleSerializer::getRuleStartIndexes($this->getIdRule());
276
        $this->executeStarts($starts);
277
278
        // execute main validation block(s)
279
        if (array_key_exists(DI::KEYWORD_DATA, $jsonData) === true &&
280
            array_key_exists(DI::KEYWORD_ID, $data = $jsonData[DI::KEYWORD_DATA]) === true
281
        ) {
282
            $index = JsonApiRuleSerializer::getRuleIndex($this->getIdRule());
283
            $this->executeBlock($data[DI::KEYWORD_ID], $index);
284
        }
285
286
        // execute end(s)
287
        $ends = JsonApiRuleSerializer::getRuleEndIndexes($this->getIdRule());
288
        $this->executeEnds($ends);
289
290
        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
            $title = $this->formatMessage(ErrorCodes::INVALID_VALUE);
292
            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
                $this->getJsonApiErrorCollection()
294
                    ->addDataIdError($title, $this->getMessage($error), $this->getErrorStatus());
295
            }
296
            $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
        return $this;
300
    }
301
302
    /**
303
     * @param array $jsonData
304
     *
305
     * @return self
306
     *
307
     * @SuppressWarnings(PHPMD.StaticAccess)
308
     * @SuppressWarnings(PHPMD.ElseExpression)
309
     */
310
    private function validateAttributes(array $jsonData): self
311
    {
312
        // execute start(s)
313
        $starts = JsonApiRuleSerializer::getRulesStartIndexes($this->getAttributeRules());
314
        $this->executeStarts($starts);
315
316
        if (array_key_exists(DI::KEYWORD_DATA, $jsonData) === true &&
317
            array_key_exists(DI::KEYWORD_ATTRIBUTES, $data = $jsonData[DI::KEYWORD_DATA]) === true
318
        ) {
319
            if (is_array($attributes = $data[DI::KEYWORD_ATTRIBUTES]) === false) {
320
                $title   = $this->formatMessage(ErrorCodes::INVALID_VALUE);
321
                $details = $this->formatMessage(ErrorCodes::INVALID_ATTRIBUTES);
322
                $this->getJsonApiErrorCollection()->addAttributesError($title, $details, $this->getErrorStatus());
323
            } else {
324
                // execute main validation block(s)
325
                foreach ($attributes as $name => $value) {
326
                    if (($index = $this->getAttributeIndex($name)) !== null) {
327
                        $this->executeBlock($value, $index);
328
                    } elseif ($this->isIgnoreUnknowns() === false) {
329
                        $title   = $this->formatMessage(ErrorCodes::INVALID_VALUE);
330
                        $details = $this->formatMessage(ErrorCodes::UNKNOWN_ATTRIBUTE);
331
                        $status  = $this->getErrorStatus();
332
                        $this->getJsonApiErrorCollection()->addDataAttributeError($name, $title, $details, $status);
333
                    }
334
                }
335
            }
336
        }
337
338
        // execute end(s)
339
        $ends = JsonApiRuleSerializer::getRulesEndIndexes($this->getAttributeRules());
340
        $this->executeEnds($ends);
341
342
        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
            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
                $this->getJsonApiErrorCollection()->addValidationAttributeError($error);
345
            }
346
            $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
        return $this;
350
    }
351
352
    /**
353
     * @param array $jsonData
354
     *
355
     * @return self
356
     *
357
     * @SuppressWarnings(PHPMD.StaticAccess)
358
     * @SuppressWarnings(PHPMD.ElseExpression)
359
     */
360
    private function validateRelationships(array $jsonData): self
361
    {
362
        // execute start(s)
363
        $starts = array_merge(
364
            JsonApiRuleSerializer::getRulesStartIndexes($this->getToOneRules()),
365
            JsonApiRuleSerializer::getRulesStartIndexes($this->getToManyRules())
366
        );
367
        $this->executeStarts($starts);
368
369
        if (array_key_exists(DI::KEYWORD_DATA, $jsonData) === true &&
370
            array_key_exists(DI::KEYWORD_RELATIONSHIPS, $data = $jsonData[DI::KEYWORD_DATA]) === true
371
        ) {
372
            if (is_array($relationships = $data[DI::KEYWORD_RELATIONSHIPS]) === false) {
373
                $title   = $this->formatMessage(ErrorCodes::INVALID_VALUE);
374
                $details = $this->formatMessage(ErrorCodes::INVALID_RELATIONSHIP_TYPE);
375
                $this->getJsonApiErrorCollection()->addRelationshipsError($title, $details, $this->getErrorStatus());
376
            } else {
377
                // ok we got to something that could be null or a valid relationship
378
                $toOneIndexes  = JsonApiRuleSerializer::getRulesIndexes($this->getToOneRules());
379
                $toManyIndexes = JsonApiRuleSerializer::getRulesIndexes($this->getToManyRules());
380
381
                foreach ($relationships as $name => $relationship) {
382
                    if (array_key_exists($name, $toOneIndexes) === true) {
383
                        // it might be to1 relationship
384
                        $this->validateAsToOneRelationship($toOneIndexes[$name], $name, $relationship);
385
                    } elseif (array_key_exists($name, $toManyIndexes) === true) {
386
                        // it might be toMany relationship
387
                        $this->validateAsToManyRelationship($toManyIndexes[$name], $name, $relationship);
388
                    } else {
389
                        // unknown relationship
390
                        $title   = $this->formatMessage(ErrorCodes::INVALID_VALUE);
391
                        $details = $this->formatMessage(ErrorCodes::UNKNOWN_RELATIONSHIP);
392
                        $status  = $this->getErrorStatus();
393
                        $this->getJsonApiErrorCollection()->addRelationshipError($name, $title, $details, $status);
394
                    }
395
                }
396
            }
397
        }
398
399
        // execute end(s)
400
        $ends = array_merge(
401
            JsonApiRuleSerializer::getRulesEndIndexes($this->getToOneRules()),
402
            JsonApiRuleSerializer::getRulesEndIndexes($this->getToManyRules())
403
        );
404
        $this->executeEnds($ends);
405
406
        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
            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
                $this->getJsonApiErrorCollection()->addValidationRelationshipError($error);
409
            }
410
            $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
        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
    private function validateAsToOneRelationship(int $index, string $name, $mightBeRelationship): void
426
    {
427
        if (is_array($mightBeRelationship) === true &&
428
            array_key_exists(DI::KEYWORD_DATA, $mightBeRelationship) === true &&
429
            ($parsed = $this->parseSingleRelationship($mightBeRelationship[DI::KEYWORD_DATA])) !== false
430
        ) {
431
            // All right we got something. Now pass it to a validation rule.
432
            $this->executeBlock($parsed, $index);
433
        } else {
434
            $title   = $this->formatMessage(ErrorCodes::INVALID_VALUE);
435
            $details = $this->formatMessage(ErrorCodes::INVALID_RELATIONSHIP);
436
            $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
    private function validateAsToManyRelationship(int $index, string $name, $mightBeRelationship): void
450
    {
451
        $isParsed       = true;
452
        $collectedPairs = [];
453
        if (is_array($mightBeRelationship) === true &&
454
            array_key_exists(DI::KEYWORD_DATA, $mightBeRelationship) === true &&
455
            is_array($data = $mightBeRelationship[DI::KEYWORD_DATA]) === true
456
        ) {
457
            foreach ($data as $mightTypeAndId) {
458
                // we accept only pairs of type and id (no `null`s are accepted).
459
                if (is_array($parsed = $this->parseSingleRelationship($mightTypeAndId)) === true) {
460
                    $collectedPairs[] = $parsed;
461
                } else {
462
                    $isParsed = false;
463
                    break;
464
                }
465
            }
466
        } else {
467
            $isParsed = false;
468
        }
469
470
        if ($isParsed === true) {
471
            // All right we got something. Now pass it to a validation rule.
472
            $this->executeBlock($collectedPairs, $index);
473
        } else {
474
            $title   = $this->formatMessage(ErrorCodes::INVALID_VALUE);
475
            $details = $this->formatMessage(ErrorCodes::INVALID_RELATIONSHIP);
476
            $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
    private function parseSingleRelationship($data)
488
    {
489
        if ($data === null) {
490
            $result = null;
491
        } elseif (is_array($data) === true &&
492
            array_key_exists(DI::KEYWORD_TYPE, $data) === true &&
493
            array_key_exists(DI::KEYWORD_ID, $data) === true &&
494
            is_scalar($type = $data[DI::KEYWORD_TYPE]) === true &&
495
            is_scalar($index = $data[DI::KEYWORD_ID]) === true
496
        ) {
497
            $result = [$type => $index];
498
        } else {
499
            $result = false;
500
        }
501
502
        return $result;
503
    }
504
505
    /**
506
     * Re-initializes internal aggregators for captures, errors, etc.
507
     */
508 8
    private function reInitAggregatorsIfNeeded(): void
509
    {
510 8
        $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
    private function executeBlock($input, int $index): void
522
    {
523
        BlockInterpreter::executeBlock(
524
            $input,
525
            $index,
526
            $this->getBlocks(),
527
            $this->getContextStorage(),
528
            $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
            $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 7
    private function executeStarts(array $indexes): void
541
    {
542 7
        BlockInterpreter::executeStarts(
543 7
            $indexes,
544 7
            $this->getBlocks(),
545 7
            $this->getContextStorage(),
546 7
            $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
    private function executeEnds(array $indexes): void
558
    {
559
        BlockInterpreter::executeEnds(
560
            $indexes,
561
            $this->getBlocks(),
562
            $this->getContextStorage(),
563
            $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
    private function getMessage(ErrorInterface $error): string
573
    {
574
        $context = $error->getMessageContext();
575
        $args    = $context === null ? [] : $context;
576
        $message = $this->formatMessage($error->getMessageCode(), $args);
577
578
        return $message;
579
    }
580
581
    /**
582
     * @return array
583
     */
584
    protected function getIdRule(): array
585
    {
586
        return $this->idRule;
587
    }
588
589
    /**
590
     * @return array
591
     */
592 7
    protected function getTypeRule(): array
593
    {
594 7
        return $this->typeRule;
595
    }
596
597
    /**
598
     * @return ContextStorageInterface
599
     */
600 7
    protected function getContextStorage(): ContextStorageInterface
601
    {
602 7
        return $this->contextStorage;
603
    }
604
605
    /**
606
     * @return ContextStorageInterface
607
     */
608 9
    protected function createContextStorage(): ContextStorageInterface
609
    {
610 9
        return new ContextStorage($this->getContainer(), $this->getBlocks());
611
    }
612
613
    /**
614
     * @return JsonApiErrorCollection
615
     */
616 1
    protected function getJsonApiErrorCollection(): JsonApiErrorCollection
617
    {
618 1
        return $this->jsonApiErrors;
619
    }
620
621
    /**
622
     * @return JsonApiErrorCollection
623
     */
624 9
    protected function createJsonApiErrors(): JsonApiErrorCollection
625
    {
626 9
        return new JsonApiErrorCollection($this->getContainer(), $this->getErrorStatus());
627
    }
628
629
    /**
630
     * @return int
631
     */
632 9
    protected function getErrorStatus(): int
633
    {
634 9
        return $this->errorStatus;
635
    }
636
637
    /**
638
     * @return bool
639
     */
640
    protected function isIgnoreUnknowns(): bool
641
    {
642
        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 9
    protected function disableIgnoreUnknowns(): self
659
    {
660 9
        $this->isIgnoreUnknowns = false;
661
662 9
        return $this;
663
    }
664
665
    /**
666
     * @param array $rules
667
     *
668
     * @return self
669
     */
670 9
    private function setAttributeRules(array $rules): self
671
    {
672 9
        assert($this->debugCheckIndexesExist($rules));
673
674 9
        $this->attributeRules = $rules;
675
676 9
        return $this;
677
    }
678
679
    /**
680
     * @param array $rules
681
     *
682
     * @return self
683
     */
684 9
    private function setToOneIndexes(array $rules): self
685
    {
686 9
        assert($this->debugCheckIndexesExist($rules));
687
688 9
        $this->toOneRules = $rules;
689
690 9
        return $this;
691
    }
692
693
    /**
694
     * @param array $rules
695
     *
696
     * @return self
697
     */
698 9
    private function setToManyIndexes(array $rules): self
699
    {
700 9
        assert($this->debugCheckIndexesExist($rules));
701
702 9
        $this->toManyRules = $rules;
703
704 9
        return $this;
705
    }
706
707
    /**
708
     * @return int[]
709
     */
710
    protected function getAttributeRules(): array
711
    {
712
        return $this->attributeRules;
713
    }
714
715
    /**
716
     * @return int[]
717
     */
718
    protected function getToOneRules(): array
719
    {
720
        return $this->toOneRules;
721
    }
722
723
    /**
724
     * @return int[]
725
     */
726
    protected function getToManyRules(): array
727
    {
728
        return $this->toManyRules;
729
    }
730
731
    /**
732
     * @return array
733
     */
734 9
    private function getBlocks(): array
735
    {
736 9
        return $this->blocks;
737
    }
738
739
    /**
740
     * @return FormatterInterface
741
     */
742 1
    protected function getMessageFormatter(): FormatterInterface
743
    {
744 1
        if ($this->messageFormatter === null) {
745
            /** @var FormatterFactoryInterface $factory */
746 1
            $factory                = $this->getContainer()->get(FormatterFactoryInterface::class);
747 1
            $this->messageFormatter = $factory->createFormatter(static::RESOURCES_NAMESPACE);
748
        }
749
750 1
        return $this->messageFormatter;
751
    }
752
753
    /**
754
     * @param string $name
755
     *
756
     * @return int|null
757
     *
758
     * @SuppressWarnings(PHPMD.StaticAccess)
759
     */
760
    private function getAttributeIndex(string $name): ?int
761
    {
762
        $indexes = JsonApiRuleSerializer::getRulesIndexes($this->getAttributeRules());
763
        $index   = $indexes[$name] ?? null;
764
765
        return $index;
766
    }
767
768
    /**
769
     * @param int   $messageId
770
     * @param array $args
771
     *
772
     * @return string
773
     */
774 1
    private function formatMessage(int $messageId, array $args = []): string
775
    {
776 1
        $message = $this->getMessageFormatter()->formatMessage($messageId, $args);
777
778 1
        return $message;
779
    }
780
781
    /**
782
     * @param array $rules
783
     *
784
     * @return bool
785
     *
786
     * @SuppressWarnings(PHPMD.StaticAccess)
787
     */
788 9
    private function debugCheckIndexesExist(array $rules): bool
789
    {
790 9
        $allOk = true;
791
792 9
        $indexes = array_merge(
793 9
            JsonApiRuleSerializer::getRulesIndexes($rules),
794 9
            JsonApiRuleSerializer::getRulesStartIndexes($rules),
795 9
            JsonApiRuleSerializer::getRulesEndIndexes($rules)
796
        );
797
798 9
        foreach ($indexes as $index) {
799 6
            $allOk = $allOk && is_int($index) && JsonApiRuleSerializer::isRuleExist($index, $this->getBlocks());
800
        }
801
802 9
        return $allOk;
803
    }
804
}
805