Completed
Push — master ( 1a67d2...dfab50 )
by Neomerx
11:35 queued 22s
created

DataParser::validateAttributes()   D

Complexity

Conditions 9
Paths 6

Size

Total Lines 41
Code Lines 25

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 24
CRAP Score 9

Importance

Changes 0
Metric Value
dl 0
loc 41
rs 4.909
c 0
b 0
f 0
ccs 24
cts 24
cp 1
cc 9
eloc 25
nc 6
nop 1
crap 9
1
<?php namespace Limoncello\Flute\Validation\JsonApi;
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\Contracts\L10n\FormatterFactoryInterface;
20
use Limoncello\Contracts\L10n\FormatterInterface;
21
use Limoncello\Flute\Contracts\Validation\ErrorCodes;
22
use Limoncello\Flute\Contracts\Validation\JsonApiDataRulesSerializerInterface;
23
use Limoncello\Flute\Contracts\Validation\JsonApiDataValidatingParserInterface;
24
use Limoncello\Flute\Http\JsonApiResponse;
25
use Limoncello\Flute\Package\FluteSettings;
26
use Limoncello\Flute\Validation\JsonApi\Execution\JsonApiErrorCollection;
27
use Limoncello\Flute\Validation\JsonApi\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\Validator\BaseValidator;
32
use Neomerx\JsonApi\Contracts\Document\DocumentInterface as DI;
33
use Neomerx\JsonApi\Exceptions\JsonApiException;
34
35
/**
36
 * @package Limoncello\Flute
37
 *
38
 * @SuppressWarnings(PHPMD.CouplingBetweenObjects)
39
 * @SuppressWarnings(PHPMD.ExcessiveClassComplexity)
40
 */
41
class DataParser extends BaseValidator implements JsonApiDataValidatingParserInterface
42
{
43
    use RelationshipsTrait;
44
45
    /** Rule description index */
46
    const RULE_INDEX = 0;
47
48
    /** Rule description index */
49
    const RULE_ATTRIBUTES = self::RULE_INDEX + 1;
50
51
    /** Rule description index */
52
    const RULE_TO_ONE = self::RULE_ATTRIBUTES + 1;
53
54
    /** Rule description index */
55
    const RULE_TO_MANY = self::RULE_TO_ONE + 1;
56
57
    /** Rule description index */
58
    const RULE_UNLISTED_ATTRIBUTE = self::RULE_TO_MANY + 1;
59
60
    /** Rule description index */
61
    const RULE_UNLISTED_RELATIONSHIP = self::RULE_UNLISTED_ATTRIBUTE + 1;
62
63
    /**
64
     * NOTE: Despite the type it is just a string so only static methods can be called from the interface.
65
     *
66
     * @var JsonApiDataRulesSerializerInterface|string
67
     */
68
    private $serializerClass;
69
70
    /**
71
     * @var int
72
     */
73
    private $errorStatus;
74
75
    /**
76
     * @var ContextStorageInterface
77
     */
78
    private $context;
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 $formatter;
124
125
    /**
126
     * @var FormatterFactoryInterface
127
     */
128
    private $formatterFactory;
129
130
    /**
131
     * @param string                    $rulesClass
132
     * @param string                    $serializerClass
133
     * @param array                     $serializedData
134
     * @param ContextStorageInterface   $context
135
     * @param JsonApiErrorCollection    $jsonErrors
136
     * @param FormatterFactoryInterface $formatterFactory
137
     * @param int                       $errorStatus
138
     *
139
     * @SuppressWarnings(PHPMD.StaticAccess)
140
     */
141 21
    public function __construct(
142
        string $rulesClass,
143
        string $serializerClass,
144
        array $serializedData,
145
        ContextStorageInterface $context,
146
        JsonApiErrorCollection $jsonErrors,
147
        FormatterFactoryInterface $formatterFactory,
148
        int $errorStatus = JsonApiResponse::HTTP_UNPROCESSABLE_ENTITY
149
    ) {
150
        $this
151 21
            ->setSerializerClass($serializerClass)
152 21
            ->setContext($context)
153 21
            ->setJsonApiErrors($jsonErrors)
154 21
            ->setFormatterFactory($formatterFactory);
155
156 21
        $this->blocks      = $this->getSerializer()::readBlocks($serializedData);
157 21
        $ruleSet           = $this->getSerializer()::readRules($rulesClass, $serializedData);
158 21
        $this->idRule      = $this->getSerializer()::readIdRuleIndexes($ruleSet);
159 21
        $this->typeRule    = $this->getSerializer()::readTypeRuleIndexes($ruleSet);
160 21
        $this->errorStatus = $errorStatus;
161
162
        $this
163 21
            ->setAttributeRules($this->getSerializer()::readAttributeRulesIndexes($ruleSet))
164 21
            ->setToOneIndexes($this->getSerializer()::readToOneRulesIndexes($ruleSet))
165 21
            ->setToManyIndexes($this->getSerializer()::readToManyRulesIndexes($ruleSet))
166 21
            ->disableIgnoreUnknowns();
167
168 21
        parent::__construct();
169
    }
170
171
    /**
172
     * @inheritdoc
173
     */
174 12
    public function assert($jsonData): JsonApiDataValidatingParserInterface
175
    {
176 12
        if ($this->validate($jsonData) === false) {
177 6
            throw new JsonApiException($this->getJsonApiErrorCollection(), $this->getErrorStatus());
178
        }
179
180 6
        return $this;
181
    }
182
183
    /**
184
     * @inheritdoc
185
     */
186 12
    public function validate($input): bool
187
    {
188 12
        if (is_array($input) === true) {
189 11
            return $this->parse($input);
190
        }
191
192 1
        $title   = $this->formatMessage(ErrorCodes::INVALID_VALUE);
193 1
        $details = $this->formatMessage(ErrorCodes::INVALID_VALUE);
194 1
        $this->getJsonApiErrorCollection()->addDataError($title, $details, $this->getErrorStatus());
195
196 1
        return false;
197
    }
198
199
    /**
200
     * @inheritdoc
201
     *
202
     * @SuppressWarnings(PHPMD.ElseExpression)
203
     */
204 15
    public function parse(array $input): bool
205
    {
206 15
        $this->reInitAggregatorsIfNeeded();
207
208
        $this
209 15
            ->validateType($input)
210 15
            ->validateId($input)
211 15
            ->validateAttributes($input)
212 15
            ->validateRelationships($input);
213
214 15
        $hasNoErrors = $this->getJsonApiErrorCollection()->count() <= 0;
215
216 15
        return $hasNoErrors;
217
    }
218
219
    /**
220
     * @inheritdoc
221
     */
222 4
    public function getJsonApiErrors(): array
223
    {
224 4
        return $this->getJsonApiErrorCollection()->getArrayCopy();
225
    }
226
227
    /**
228
     * @inheritdoc
229
     */
230 9
    public function getJsonApiCaptures(): array
231
    {
232 9
        return $this->getCaptures();
233
    }
234
235
    /**
236
     * @return BaseValidator
0 ignored issues
show
Documentation introduced by
Consider making the return type a bit more specific; maybe use DataParser.

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...
237
     */
238 21
    protected function resetAggregators(): BaseValidator
239
    {
240 21
        $self = parent::resetAggregators();
241
242 21
        $this->getContext()->clear();
243
244 21
        return $self;
245
    }
246
247
    /**
248
     * @param string $serializerClass
249
     *
250
     * @return self
251
     */
252 21
    protected function setSerializerClass(string $serializerClass): self
253
    {
254 21
        assert(
255 21
            class_exists($serializerClass) === true &&
256 21
            in_array(JsonApiDataRulesSerializerInterface::class, class_implements($serializerClass)) === true
257
        );
258
259 21
        $this->serializerClass = $serializerClass;
260
261 21
        return $this;
262
    }
263
264
    /**
265
     * @return JsonApiDataRulesSerializerInterface|string
266
     */
267 21
    protected function getSerializer()
268
    {
269 21
        return $this->serializerClass;
270
    }
271
272
    /**
273
     * @param array $jsonData
274
     *
275
     * @return self
276
     *
277
     * @SuppressWarnings(PHPMD.StaticAccess)
278
     * @SuppressWarnings(PHPMD.ElseExpression)
279
     */
280 15
    private function validateType(array $jsonData): self
281
    {
282
        // execute start(s)
283 15
        $starts = $this->getSerializer()::readRuleStartIndexes($this->getTypeRule());
284 15
        $this->executeStarts($starts);
285
286 15
        if (array_key_exists(DI::KEYWORD_DATA, $jsonData) === true &&
287 15
            array_key_exists(DI::KEYWORD_TYPE, $data = $jsonData[DI::KEYWORD_DATA]) === true
288
        ) {
289
            // execute main validation block(s)
290 14
            $index = $this->getSerializer()::readRuleIndex($this->getTypeRule());
291 14
            $this->executeBlock($data[DI::KEYWORD_TYPE], $index);
292
        } else {
293 1
            $title   = $this->formatMessage(ErrorCodes::INVALID_VALUE);
294 1
            $details = $this->formatMessage(ErrorCodes::TYPE_MISSING);
295 1
            $this->getJsonApiErrorCollection()->addDataTypeError($title, $details, $this->getErrorStatus());
296
        }
297
298
        // execute end(s)
299 15
        $ends = $this->getSerializer()::readRuleEndIndexes($this->getTypeRule());
300 15
        $this->executeEnds($ends);
301
302 15
        if (count($this->getErrorAggregator()) > 0) {
303 1
            $title = $this->formatMessage(ErrorCodes::INVALID_VALUE);
304 1
            foreach ($this->getErrorAggregator()->get() as $error) {
305 1
                $this->getJsonApiErrorCollection()
306 1
                    ->addDataTypeError($title, $this->getMessage($error), $this->getErrorStatus());
307
            }
308 1
            $this->getErrorAggregator()->clear();
309
        }
310
311 15
        return $this;
312
    }
313
314
    /**
315
     * @param array $jsonData
316
     *
317
     * @return self
318
     *
319
     * @SuppressWarnings(PHPMD.StaticAccess)
320
     */
321 15
    private function validateId(array $jsonData): self
322
    {
323
        // execute start(s)
324 15
        $starts = $this->getSerializer()::readRuleStartIndexes($this->getIdRule());
325 15
        $this->executeStarts($starts);
326
327
        // execute main validation block(s)
328 15
        if (array_key_exists(DI::KEYWORD_DATA, $jsonData) === true &&
329 15
            array_key_exists(DI::KEYWORD_ID, $data = $jsonData[DI::KEYWORD_DATA]) === true
330
        ) {
331 13
            $index = $this->getSerializer()::readRuleIndex($this->getIdRule());
332 13
            $this->executeBlock($data[DI::KEYWORD_ID], $index);
333
        }
334
335
        // execute end(s)
336 15
        $ends = $this->getSerializer()::readRuleEndIndexes($this->getIdRule());
337 15
        $this->executeEnds($ends);
338
339 15
        if (count($this->getErrorAggregator()) > 0) {
340 3
            $title = $this->formatMessage(ErrorCodes::INVALID_VALUE);
341 3
            foreach ($this->getErrorAggregator()->get() as $error) {
342 3
                $this->getJsonApiErrorCollection()
343 3
                    ->addDataIdError($title, $this->getMessage($error), $this->getErrorStatus());
344
            }
345 3
            $this->getErrorAggregator()->clear();
346
        }
347
348 15
        return $this;
349
    }
350
351
    /**
352
     * @param array $jsonData
353
     *
354
     * @return self
355
     *
356
     * @SuppressWarnings(PHPMD.StaticAccess)
357
     * @SuppressWarnings(PHPMD.ElseExpression)
358
     */
359 15
    private function validateAttributes(array $jsonData): self
360
    {
361
        // execute start(s)
362 15
        $starts = $this->getSerializer()::readRulesStartIndexes($this->getAttributeRules());
363 15
        $this->executeStarts($starts);
364
365 15
        if (array_key_exists(DI::KEYWORD_DATA, $jsonData) === true &&
366 15
            array_key_exists(DI::KEYWORD_ATTRIBUTES, $data = $jsonData[DI::KEYWORD_DATA]) === true
367
        ) {
368 14
            if (is_array($attributes = $data[DI::KEYWORD_ATTRIBUTES]) === false) {
369 2
                $title   = $this->formatMessage(ErrorCodes::INVALID_VALUE);
370 2
                $details = $this->formatMessage(ErrorCodes::INVALID_ATTRIBUTES);
371 2
                $this->getJsonApiErrorCollection()->addAttributesError($title, $details, $this->getErrorStatus());
372
            } else {
373
                // execute main validation block(s)
374 12
                foreach ($attributes as $name => $value) {
375 9
                    if (($index = $this->getAttributeIndex($name)) !== null) {
376 8
                        $this->executeBlock($value, $index);
377 1
                    } elseif ($this->isIgnoreUnknowns() === false) {
378 1
                        $title   = $this->formatMessage(ErrorCodes::INVALID_VALUE);
379 1
                        $details = $this->formatMessage(ErrorCodes::UNKNOWN_ATTRIBUTE);
380 1
                        $status  = $this->getErrorStatus();
381 9
                        $this->getJsonApiErrorCollection()->addDataAttributeError($name, $title, $details, $status);
382
                    }
383
                }
384
            }
385
        }
386
387
        // execute end(s)
388 15
        $ends = $this->getSerializer()::readRulesEndIndexes($this->getAttributeRules());
389 15
        $this->executeEnds($ends);
390
391 15
        if (count($this->getErrorAggregator()) > 0) {
392 2
            foreach ($this->getErrorAggregator()->get() as $error) {
393 2
                $this->getJsonApiErrorCollection()->addValidationAttributeError($error);
394
            }
395 2
            $this->getErrorAggregator()->clear();
396
        }
397
398 15
        return $this;
399
    }
400
401
    /**
402
     * @param array $jsonData
403
     *
404
     * @return self
405
     *
406
     * @SuppressWarnings(PHPMD.StaticAccess)
407
     * @SuppressWarnings(PHPMD.ElseExpression)
408
     */
409 15
    private function validateRelationships(array $jsonData): self
410
    {
411
        // execute start(s)
412 15
        $starts = array_merge(
413 15
            $this->getSerializer()::readRulesStartIndexes($this->getToOneRules()),
414 15
            $this->getSerializer()::readRulesStartIndexes($this->getToManyRules())
415
        );
416 15
        $this->executeStarts($starts);
417
418 15
        if (array_key_exists(DI::KEYWORD_DATA, $jsonData) === true &&
419 15
            array_key_exists(DI::KEYWORD_RELATIONSHIPS, $data = $jsonData[DI::KEYWORD_DATA]) === true
420
        ) {
421 10
            if (is_array($relationships = $data[DI::KEYWORD_RELATIONSHIPS]) === false) {
422 1
                $title   = $this->formatMessage(ErrorCodes::INVALID_VALUE);
423 1
                $details = $this->formatMessage(ErrorCodes::INVALID_RELATIONSHIP_TYPE);
424 1
                $this->getJsonApiErrorCollection()->addRelationshipsError($title, $details, $this->getErrorStatus());
425
            } else {
426
                // ok we got to something that could be null or a valid relationship
427 9
                $toOneIndexes  = $this->getSerializer()::readRulesIndexes($this->getToOneRules());
428 9
                $toManyIndexes = $this->getSerializer()::readRulesIndexes($this->getToManyRules());
429
430 9
                foreach ($relationships as $name => $relationship) {
431 9
                    if (array_key_exists($name, $toOneIndexes) === true) {
432
                        // it might be to1 relationship
433 8
                        $this->validateAsToOneRelationship($toOneIndexes[$name], $name, $relationship);
434 9
                    } elseif (array_key_exists($name, $toManyIndexes) === true) {
435
                        // it might be toMany relationship
436 8
                        $this->validateAsToManyRelationship($toManyIndexes[$name], $name, $relationship);
437
                    } else {
438
                        // unknown relationship
439 1
                        $title   = $this->formatMessage(ErrorCodes::INVALID_VALUE);
440 1
                        $details = $this->formatMessage(ErrorCodes::UNKNOWN_RELATIONSHIP);
441 1
                        $status  = $this->getErrorStatus();
442 9
                        $this->getJsonApiErrorCollection()->addRelationshipError($name, $title, $details, $status);
443
                    }
444
                }
445
            }
446
        }
447
448
        // execute end(s)
449 15
        $ends = array_merge(
450 15
            $this->getSerializer()::readRulesEndIndexes($this->getToOneRules()),
451 15
            $this->getSerializer()::readRulesEndIndexes($this->getToManyRules())
452
        );
453 15
        $this->executeEnds($ends);
454
455 15
        if (count($this->getErrorAggregator()) > 0) {
456 3
            foreach ($this->getErrorAggregator()->get() as $error) {
457 3
                $this->getJsonApiErrorCollection()->addValidationRelationshipError($error);
458
            }
459 3
            $this->getErrorAggregator()->clear();
460
        }
461
462 15
        return $this;
463
    }
464
465
    /**
466
     * @param int    $index
467
     * @param string $name
468
     * @param mixed  $mightBeRelationship
469
     *
470
     * @return void
471
     *
472
     * @SuppressWarnings(PHPMD.ElseExpression)
473
     */
474 8
    private function validateAsToOneRelationship(int $index, string $name, $mightBeRelationship): void
475
    {
476 8
        if (is_array($mightBeRelationship) === true &&
477 8
            array_key_exists(DI::KEYWORD_DATA, $mightBeRelationship) === true &&
478 8
            ($parsed = $this->parseSingleRelationship($mightBeRelationship[DI::KEYWORD_DATA])) !== false
479
        ) {
480
            // All right we got something. Now pass it to a validation rule.
481 6
            $this->executeBlock($parsed, $index);
482
        } else {
483 2
            $title   = $this->formatMessage(ErrorCodes::INVALID_VALUE);
484 2
            $details = $this->formatMessage(ErrorCodes::INVALID_RELATIONSHIP);
485 2
            $this->getJsonApiErrorCollection()->addRelationshipError($name, $title, $details, $this->getErrorStatus());
486
        }
487
    }
488
489
    /**
490
     * @param int    $index
491
     * @param string $name
492
     * @param mixed  $mightBeRelationship
493
     *
494
     * @return void
495
     *
496
     * @SuppressWarnings(PHPMD.ElseExpression)
497
     */
498 8
    private function validateAsToManyRelationship(int $index, string $name, $mightBeRelationship): void
499
    {
500 8
        $isParsed       = true;
501 8
        $collectedPairs = [];
502 8
        if (is_array($mightBeRelationship) === true &&
503 8
            array_key_exists(DI::KEYWORD_DATA, $mightBeRelationship) === true &&
504 8
            is_array($data = $mightBeRelationship[DI::KEYWORD_DATA]) === true
505
        ) {
506 6
            foreach ($data as $mightTypeAndId) {
507
                // we accept only pairs of type and id (no `null`s are accepted).
508 5
                if (is_array($parsed = $this->parseSingleRelationship($mightTypeAndId)) === true) {
509 4
                    $collectedPairs[] = $parsed;
510
                } else {
511 1
                    $isParsed = false;
512 6
                    break;
513
                }
514
            }
515
        } else {
516 2
            $isParsed = false;
517
        }
518
519 8
        if ($isParsed === true) {
520
            // All right we got something. Now pass it to a validation rule.
521 5
            $this->executeBlock($collectedPairs, $index);
522
        } else {
523 3
            $title   = $this->formatMessage(ErrorCodes::INVALID_VALUE);
524 3
            $details = $this->formatMessage(ErrorCodes::INVALID_RELATIONSHIP);
525 3
            $this->getJsonApiErrorCollection()->addRelationshipError($name, $title, $details, $this->getErrorStatus());
526
        }
527
    }
528
529
    /**
530
     * @param mixed $data
531
     *
532
     * @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...
533
     *
534
     * @SuppressWarnings(PHPMD.ElseExpression)
535
     */
536 7
    private function parseSingleRelationship($data)
537
    {
538 7
        if ($data === null) {
539 2
            $result = null;
540 5
        } elseif (is_array($data) === true &&
541 5
            array_key_exists(DI::KEYWORD_TYPE, $data) === true &&
542 5
            array_key_exists(DI::KEYWORD_ID, $data) === true &&
543 5
            is_scalar($type = $data[DI::KEYWORD_TYPE]) === true &&
544 5
            is_scalar($index = $data[DI::KEYWORD_ID]) === true
545
        ) {
546 4
            $result = [$type => $index];
547
        } else {
548 1
            $result = false;
549
        }
550
551 7
        return $result;
552
    }
553
554
    /**
555
     * Re-initializes internal aggregators for captures, errors, etc.
556
     */
557 15
    private function reInitAggregatorsIfNeeded(): void
558
    {
559 15
        $this->areAggregatorsDirty() === false ?: $this->resetAggregators();
560
    }
561
562
    /**
563
     * @param mixed $input
564
     * @param int   $index
565
     *
566
     * @return void
567
     *
568
     * @SuppressWarnings(PHPMD.StaticAccess)
569
     */
570 14
    private function executeBlock($input, int $index): void
571
    {
572 14
        BlockInterpreter::executeBlock(
573 14
            $input,
574 14
            $index,
575 14
            $this->getBlocks(),
576 14
            $this->getContext(),
577 14
            $this->getCaptureAggregator(),
578 14
            $this->getErrorAggregator()
579
        );
580
    }
581
582
    /**
583
     * @param array $indexes
584
     *
585
     * @return void
586
     *
587
     * @SuppressWarnings(PHPMD.StaticAccess)
588
     */
589 15
    private function executeStarts(array $indexes): void
590
    {
591 15
        BlockInterpreter::executeStarts(
592 15
            $indexes,
593 15
            $this->getBlocks(),
594 15
            $this->getContext(),
595 15
            $this->getErrorAggregator()
596
        );
597
    }
598
599
    /**
600
     * @param array $indexes
601
     *
602
     * @return void
603
     *
604
     * @SuppressWarnings(PHPMD.StaticAccess)
605
     */
606 15
    private function executeEnds(array $indexes): void
607
    {
608 15
        BlockInterpreter::executeEnds(
609 15
            $indexes,
610 15
            $this->getBlocks(),
611 15
            $this->getContext(),
612 15
            $this->getErrorAggregator()
613
        );
614
    }
615
616
    /**
617
     * @param ErrorInterface $error
618
     *
619
     * @return string
620
     */
621 3
    private function getMessage(ErrorInterface $error): string
622
    {
623 3
        $context = $error->getMessageContext();
624 3
        $args    = $context === null ? [] : $context;
625 3
        $message = $this->formatMessage($error->getMessageCode(), $args);
626
627 3
        return $message;
628
    }
629
630
    /**
631
     * @return array
632
     */
633 15
    protected function getIdRule(): array
634
    {
635 15
        return $this->idRule;
636
    }
637
638
    /**
639
     * @return array
640
     */
641 15
    protected function getTypeRule(): array
642
    {
643 15
        return $this->typeRule;
644
    }
645
646
    /**
647
     * @return ContextStorageInterface
648
     */
649 21
    protected function getContext(): ContextStorageInterface
650
    {
651 21
        return $this->context;
652
    }
653
654
    /**
655
     * @param ContextStorageInterface $context
656
     *
657
     * @return self
658
     */
659 21
    protected function setContext(ContextStorageInterface $context): self
660
    {
661 21
        $this->context = $context;
662
663 21
        return $this;
664
    }
665
666
    /**
667
     * @return JsonApiErrorCollection
668
     */
669 16
    protected function getJsonApiErrorCollection(): JsonApiErrorCollection
670
    {
671 16
        return $this->jsonApiErrors;
672
    }
673
674
    /**
675
     * @param JsonApiErrorCollection $errors
676
     *
677
     * @return self
678
     */
679 21
    protected function setJsonApiErrors(JsonApiErrorCollection $errors): self
680
    {
681 21
        $this->jsonApiErrors = $errors;
682
683 21
        return $this;
684
    }
685
686
    /**
687
     * @return int
688
     */
689 10
    protected function getErrorStatus(): int
690
    {
691 10
        return $this->errorStatus;
692
    }
693
694
    /**
695
     * @return bool
696
     */
697 1
    protected function isIgnoreUnknowns(): bool
698
    {
699 1
        return $this->isIgnoreUnknowns;
700
    }
701
702
    /**
703
     * @return self
704
     */
705 1
    protected function enableIgnoreUnknowns(): self
706
    {
707 1
        $this->isIgnoreUnknowns = true;
708
709 1
        return $this;
710
    }
711
712
    /**
713
     * @return self
714
     */
715 21
    protected function disableIgnoreUnknowns(): self
716
    {
717 21
        $this->isIgnoreUnknowns = false;
718
719 21
        return $this;
720
    }
721
722
    /**
723
     * @param array $rules
724
     *
725
     * @return self
726
     */
727 21
    private function setAttributeRules(array $rules): self
728
    {
729 21
        assert($this->debugCheckIndexesExist($rules));
730
731 21
        $this->attributeRules = $rules;
732
733 21
        return $this;
734
    }
735
736
    /**
737
     * @param array $rules
738
     *
739
     * @return self
740
     */
741 21
    private function setToOneIndexes(array $rules): self
742
    {
743 21
        assert($this->debugCheckIndexesExist($rules));
744
745 21
        $this->toOneRules = $rules;
746
747 21
        return $this;
748
    }
749
750
    /**
751
     * @param array $rules
752
     *
753
     * @return self
754
     */
755 21
    private function setToManyIndexes(array $rules): self
756
    {
757 21
        assert($this->debugCheckIndexesExist($rules));
758
759 21
        $this->toManyRules = $rules;
760
761 21
        return $this;
762
    }
763
764
    /**
765
     * @return int[]
766
     */
767 15
    protected function getAttributeRules(): array
768
    {
769 15
        return $this->attributeRules;
770
    }
771
772
    /**
773
     * @return int[]
774
     */
775 15
    protected function getToOneRules(): array
776
    {
777 15
        return $this->toOneRules;
778
    }
779
780
    /**
781
     * @return int[]
782
     */
783 15
    protected function getToManyRules(): array
784
    {
785 15
        return $this->toManyRules;
786
    }
787
788
    /**
789
     * @return array
790
     */
791 19
    private function getBlocks(): array
792
    {
793 19
        return $this->blocks;
794
    }
795
796
    /**
797
     * @return FormatterInterface
798
     */
799 9
    protected function getFormatter(): FormatterInterface
800
    {
801 9
        if ($this->formatter === null) {
802 9
            $this->formatter = $this->formatterFactory->createFormatter(FluteSettings::VALIDATION_NAMESPACE);
803
        }
804
805 9
        return $this->formatter;
806
    }
807
808
    /**
809
     * @param FormatterFactoryInterface $formatterFactory
810
     *
811
     * @return self
812
     */
813 21
    protected function setFormatterFactory(FormatterFactoryInterface $formatterFactory): self
814
    {
815 21
        $this->formatterFactory = $formatterFactory;
816
817 21
        return $this;
818
    }
819
820
    /**
821
     * @param string $name
822
     *
823
     * @return int|null
824
     *
825
     * @SuppressWarnings(PHPMD.StaticAccess)
826
     */
827 9
    private function getAttributeIndex(string $name): ?int
828
    {
829 9
        $indexes = $this->getSerializer()::readRulesIndexes($this->getAttributeRules());
830 9
        $index   = $indexes[$name] ?? null;
831
832 9
        return $index;
833
    }
834
835
    /**
836
     * @param int   $messageId
837
     * @param array $args
838
     *
839
     * @return string
840
     */
841 9
    private function formatMessage(int $messageId, array $args = []): string
842
    {
843 9
        $message = $this->getFormatter()->formatMessage($messageId, $args);
844
845 9
        return $message;
846
    }
847
848
    /**
849
     * @param array $rules
850
     *
851
     * @return bool
852
     *
853
     * @SuppressWarnings(PHPMD.StaticAccess)
854
     */
855 21
    private function debugCheckIndexesExist(array $rules): bool
856
    {
857 21
        $allOk = true;
858
859 21
        $indexes = array_merge(
860 21
            $this->getSerializer()::readRulesIndexes($rules),
861 21
            $this->getSerializer()::readRulesStartIndexes($rules),
862 21
            $this->getSerializer()::readRulesEndIndexes($rules)
863
        );
864
865 21
        foreach ($indexes as $index) {
866 17
            $allOk = $allOk && is_int($index) && $this->getSerializer()::hasRule($index, $this->getBlocks());
867
        }
868
869 21
        return $allOk;
870
    }
871
}
872