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

Validator::parseSingleRelationship()   B

Complexity

Conditions 7
Paths 3

Size

Total Lines 17
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 11
CRAP Score 7

Importance

Changes 0
Metric Value
dl 0
loc 17
c 0
b 0
f 0
ccs 11
cts 11
cp 1
rs 8.2222
cc 7
eloc 12
nc 3
nop 1
crap 7
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\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\Package\FluteSettings;
26
use Limoncello\Flute\Validation\JsonApi\Execution\JsonApiErrorCollection;
27
use Limoncello\Flute\Validation\JsonApi\Execution\JsonApiRuleSerializer;
28
use Limoncello\Flute\Validation\JsonApi\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\Execution\ContextStorage;
33
use Limoncello\Validation\Validator\BaseValidator;
34
use Neomerx\JsonApi\Contracts\Document\DocumentInterface as DI;
35
use Neomerx\JsonApi\Exceptions\JsonApiException;
36
use Psr\Container\ContainerInterface;
37
38
/**
39
 * @package Limoncello\Flute
40
 *
41
 * @SuppressWarnings(PHPMD.CouplingBetweenObjects)
42
 * @SuppressWarnings(PHPMD.ExcessiveClassComplexity)
43
 */
44
class Validator extends BaseValidator implements JsonApiValidatorInterface
45
{
46
    use RelationshipsTrait, HasContainerTrait;
47
48
    /**
49
     * Namespace for string resources.
50
     */
51
    const RESOURCES_NAMESPACE = FluteSettings::VALIDATION_NAMESPACE;
52
53
    /** Rule description index */
54
    const RULE_INDEX = 0;
55
56
    /** Rule description index */
57
    const RULE_ATTRIBUTES = self::RULE_INDEX + 1;
58
59
    /** Rule description index */
60
    const RULE_TO_ONE = self::RULE_ATTRIBUTES + 1;
61
62
    /** Rule description index */
63
    const RULE_TO_MANY = self::RULE_TO_ONE + 1;
64
65
    /** Rule description index */
66
    const RULE_UNLISTED_ATTRIBUTE = self::RULE_TO_MANY + 1;
67
68
    /** Rule description index */
69
    const RULE_UNLISTED_RELATIONSHIP = self::RULE_UNLISTED_ATTRIBUTE + 1;
70
71
    /**
72
     * @var int
73
     */
74
    private $errorStatus;
75
76
    /**
77
     * @var ContextStorageInterface
78
     */
79
    private $contextStorage;
80
81
    /**
82
     * @var JsonApiErrorCollection
83
     */
84
    private $jsonApiErrors;
85
86
    /**
87
     * @var array
88
     */
89
    private $blocks;
90
91
    /**
92
     * @var array
93
     */
94
    private $idRule;
95
96
    /**
97
     * @var array
98
     */
99
    private $typeRule;
100
101
    /**
102
     * @var int[]
103
     */
104
    private $attributeRules;
105
106
    /**
107
     * @var int[]
108
     */
109
    private $toOneRules;
110
111
    /**
112
     * @var int[]
113
     */
114
    private $toManyRules;
115
116
    /**
117
     * @var bool
118
     */
119
    private $isIgnoreUnknowns;
120
121
    /**
122
     * @var FormatterInterface|null
123
     */
124
    private $messageFormatter;
125
126
    /**
127
     * @param string             $name
128
     * @param array              $data
129
     * @param ContainerInterface $container
130
     * @param int                $errorStatus
131
     *
132
     * @SuppressWarnings(PHPMD.StaticAccess)
133
     */
134 20
    public function __construct(
135
        string $name,
136
        array $data,
137
        ContainerInterface $container,
138
        int $errorStatus = JsonApiResponse::HTTP_UNPROCESSABLE_ENTITY
139
    ) {
140 20
        $this->setContainer($container);
141
142 20
        $ruleSet           = JsonApiRuleSerializer::extractRuleSet($name, $data);
143 20
        $this->blocks      = JsonApiRuleSerializer::extractBlocks($data);
144 20
        $this->idRule      = JsonApiRuleSerializer::getIdRule($ruleSet);
145 20
        $this->typeRule    = JsonApiRuleSerializer::getTypeRule($ruleSet);
146 20
        $this->errorStatus = $errorStatus;
147
148
        $this
149 20
            ->setAttributeRules(JsonApiRuleSerializer::getAttributeRules($ruleSet))
150 20
            ->setToOneIndexes(JsonApiRuleSerializer::getToOneRules($ruleSet))
151 20
            ->setToManyIndexes(JsonApiRuleSerializer::getToManyRules($ruleSet))
152 20
            ->disableIgnoreUnknowns();
153
154 20
        parent::__construct();
155
    }
156
157
    /**
158
     * @inheritdoc
159
     */
160 13
    public function assert($jsonData): JsonApiValidatorInterface
161
    {
162 13
        if ($this->validate($jsonData) === false) {
163 6
            throw new JsonApiException($this->getJsonApiErrorCollection(), $this->getErrorStatus());
164
        }
165
166 7
        return $this;
167
    }
168
169
    /**
170
     * @inheritdoc
171
     *
172
     * @SuppressWarnings(PHPMD.ElseExpression)
173
     */
174 19
    public function validate($input): bool
175
    {
176 19
        $this->reInitAggregatorsIfNeeded();
177
178 19
        if (is_array($input) === true) {
179
            $this
180 18
                ->validateType($input)
181 18
                ->validateId($input)
182 18
                ->validateAttributes($input)
183 18
                ->validateRelationships($input);
184
        } else {
185 1
            $title   = $this->formatMessage(ErrorCodes::INVALID_VALUE);
186 1
            $details = $this->formatMessage(ErrorCodes::INVALID_VALUE);
187 1
            $this->getJsonApiErrorCollection()->addDataError($title, $details, $this->getErrorStatus());
188
        }
189
190 19
        $hasNoErrors = $this->getJsonApiErrorCollection()->count() <= 0;
191
192 19
        return $hasNoErrors;
193
    }
194
195
    /**
196
     * @inheritdoc
197
     */
198 6
    public function getJsonApiErrors(): array
199
    {
200 6
        return $this->getJsonApiErrorCollection()->getArrayCopy();
201
    }
202
203
    /**
204
     * @inheritdoc
205
     */
206 12
    public function getJsonApiCaptures(): array
207
    {
208 12
        return $this->getCaptures();
209
    }
210
211
    /**
212
     * @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...
213
     */
214 20
    protected function resetAggregators(): BaseValidator
215
    {
216 20
        $self = parent::resetAggregators();
217
218 20
        $this->jsonApiErrors  = $this->createJsonApiErrors();
219 20
        $this->contextStorage = $this->createContextStorage();
220
221 20
        return $self;
222
    }
223
224
    /**
225
     * @param array $jsonData
226
     *
227
     * @return self
228
     *
229
     * @SuppressWarnings(PHPMD.StaticAccess)
230
     * @SuppressWarnings(PHPMD.ElseExpression)
231
     */
232 18
    private function validateType(array $jsonData): self
233
    {
234
        // execute start(s)
235 18
        $starts = JsonApiRuleSerializer::getRuleStartIndexes($this->getTypeRule());
236 18
        $this->executeStarts($starts);
237
238 18
        if (array_key_exists(DI::KEYWORD_DATA, $jsonData) === true &&
239 18
            array_key_exists(DI::KEYWORD_TYPE, $data = $jsonData[DI::KEYWORD_DATA]) === true
240
        ) {
241
            // execute main validation block(s)
242 17
            $index = JsonApiRuleSerializer::getRuleIndex($this->getTypeRule());
243 17
            $this->executeBlock($data[DI::KEYWORD_TYPE], $index);
244
        } else {
245 1
            $title   = $this->formatMessage(ErrorCodes::INVALID_VALUE);
246 1
            $details = $this->formatMessage(ErrorCodes::TYPE_MISSING);
247 1
            $this->getJsonApiErrorCollection()->addDataTypeError($title, $details, $this->getErrorStatus());
248
        }
249
250
        // execute end(s)
251 18
        $ends = JsonApiRuleSerializer::getRuleEndIndexes($this->getTypeRule());
252 18
        $this->executeEnds($ends);
253
254 18
        if (count($this->getErrorAggregator()) > 0) {
255 1
            $title = $this->formatMessage(ErrorCodes::INVALID_VALUE);
256 1
            foreach ($this->getErrorAggregator()->get() as $error) {
257 1
                $this->getJsonApiErrorCollection()
258 1
                    ->addDataTypeError($title, $this->getMessage($error), $this->getErrorStatus());
259
            }
260 1
            $this->getErrorAggregator()->clear();
261
        }
262
263 18
        return $this;
264
    }
265
266
    /**
267
     * @param array $jsonData
268
     *
269
     * @return self
270
     *
271
     * @SuppressWarnings(PHPMD.StaticAccess)
272
     */
273 18
    private function validateId(array $jsonData): self
274
    {
275
        // execute start(s)
276 18
        $starts = JsonApiRuleSerializer::getRuleStartIndexes($this->getIdRule());
277 18
        $this->executeStarts($starts);
278
279
        // execute main validation block(s)
280 18
        if (array_key_exists(DI::KEYWORD_DATA, $jsonData) === true &&
281 18
            array_key_exists(DI::KEYWORD_ID, $data = $jsonData[DI::KEYWORD_DATA]) === true
282
        ) {
283 16
            $index = JsonApiRuleSerializer::getRuleIndex($this->getIdRule());
284 16
            $this->executeBlock($data[DI::KEYWORD_ID], $index);
285
        }
286
287
        // execute end(s)
288 18
        $ends = JsonApiRuleSerializer::getRuleEndIndexes($this->getIdRule());
289 18
        $this->executeEnds($ends);
290
291 18
        if (count($this->getErrorAggregator()) > 0) {
292 3
            $title = $this->formatMessage(ErrorCodes::INVALID_VALUE);
293 3
            foreach ($this->getErrorAggregator()->get() as $error) {
294 3
                $this->getJsonApiErrorCollection()
295 3
                    ->addDataIdError($title, $this->getMessage($error), $this->getErrorStatus());
296
            }
297 3
            $this->getErrorAggregator()->clear();
298
        }
299
300 18
        return $this;
301
    }
302
303
    /**
304
     * @param array $jsonData
305
     *
306
     * @return self
307
     *
308
     * @SuppressWarnings(PHPMD.StaticAccess)
309
     * @SuppressWarnings(PHPMD.ElseExpression)
310
     */
311 18
    private function validateAttributes(array $jsonData): self
312
    {
313
        // execute start(s)
314 18
        $starts = JsonApiRuleSerializer::getRulesStartIndexes($this->getAttributeRules());
315 18
        $this->executeStarts($starts);
316
317 18
        if (array_key_exists(DI::KEYWORD_DATA, $jsonData) === true &&
318 18
            array_key_exists(DI::KEYWORD_ATTRIBUTES, $data = $jsonData[DI::KEYWORD_DATA]) === true
319
        ) {
320 17
            if (is_array($attributes = $data[DI::KEYWORD_ATTRIBUTES]) === false) {
321 2
                $title   = $this->formatMessage(ErrorCodes::INVALID_VALUE);
322 2
                $details = $this->formatMessage(ErrorCodes::INVALID_ATTRIBUTES);
323 2
                $this->getJsonApiErrorCollection()->addAttributesError($title, $details, $this->getErrorStatus());
324
            } else {
325
                // execute main validation block(s)
326 15
                foreach ($attributes as $name => $value) {
327 12
                    if (($index = $this->getAttributeIndex($name)) !== null) {
328 11
                        $this->executeBlock($value, $index);
329 1
                    } elseif ($this->isIgnoreUnknowns() === false) {
330 1
                        $title   = $this->formatMessage(ErrorCodes::INVALID_VALUE);
331 1
                        $details = $this->formatMessage(ErrorCodes::UNKNOWN_ATTRIBUTE);
332 1
                        $status  = $this->getErrorStatus();
333 12
                        $this->getJsonApiErrorCollection()->addDataAttributeError($name, $title, $details, $status);
334
                    }
335
                }
336
            }
337
        }
338
339
        // execute end(s)
340 18
        $ends = JsonApiRuleSerializer::getRulesEndIndexes($this->getAttributeRules());
341 18
        $this->executeEnds($ends);
342
343 18
        if (count($this->getErrorAggregator()) > 0) {
344 2
            foreach ($this->getErrorAggregator()->get() as $error) {
345 2
                $this->getJsonApiErrorCollection()->addValidationAttributeError($error);
346
            }
347 2
            $this->getErrorAggregator()->clear();
348
        }
349
350 18
        return $this;
351
    }
352
353
    /**
354
     * @param array $jsonData
355
     *
356
     * @return self
357
     *
358
     * @SuppressWarnings(PHPMD.StaticAccess)
359
     * @SuppressWarnings(PHPMD.ElseExpression)
360
     */
361 18
    private function validateRelationships(array $jsonData): self
362
    {
363
        // execute start(s)
364 18
        $starts = array_merge(
365 18
            JsonApiRuleSerializer::getRulesStartIndexes($this->getToOneRules()),
366 18
            JsonApiRuleSerializer::getRulesStartIndexes($this->getToManyRules())
367
        );
368 18
        $this->executeStarts($starts);
369
370 18
        if (array_key_exists(DI::KEYWORD_DATA, $jsonData) === true &&
371 18
            array_key_exists(DI::KEYWORD_RELATIONSHIPS, $data = $jsonData[DI::KEYWORD_DATA]) === true
372
        ) {
373 10
            if (is_array($relationships = $data[DI::KEYWORD_RELATIONSHIPS]) === false) {
374 1
                $title   = $this->formatMessage(ErrorCodes::INVALID_VALUE);
375 1
                $details = $this->formatMessage(ErrorCodes::INVALID_RELATIONSHIP_TYPE);
376 1
                $this->getJsonApiErrorCollection()->addRelationshipsError($title, $details, $this->getErrorStatus());
377
            } else {
378
                // ok we got to something that could be null or a valid relationship
379 9
                $toOneIndexes  = JsonApiRuleSerializer::getRulesIndexes($this->getToOneRules());
380 9
                $toManyIndexes = JsonApiRuleSerializer::getRulesIndexes($this->getToManyRules());
381
382 9
                foreach ($relationships as $name => $relationship) {
383 9
                    if (array_key_exists($name, $toOneIndexes) === true) {
384
                        // it might be to1 relationship
385 8
                        $this->validateAsToOneRelationship($toOneIndexes[$name], $name, $relationship);
386 9
                    } elseif (array_key_exists($name, $toManyIndexes) === true) {
387
                        // it might be toMany relationship
388 8
                        $this->validateAsToManyRelationship($toManyIndexes[$name], $name, $relationship);
389
                    } else {
390
                        // unknown relationship
391 1
                        $title   = $this->formatMessage(ErrorCodes::INVALID_VALUE);
392 1
                        $details = $this->formatMessage(ErrorCodes::UNKNOWN_RELATIONSHIP);
393 1
                        $status  = $this->getErrorStatus();
394 9
                        $this->getJsonApiErrorCollection()->addRelationshipError($name, $title, $details, $status);
395
                    }
396
                }
397
            }
398
        }
399
400
        // execute end(s)
401 18
        $ends = array_merge(
402 18
            JsonApiRuleSerializer::getRulesEndIndexes($this->getToOneRules()),
403 18
            JsonApiRuleSerializer::getRulesEndIndexes($this->getToManyRules())
404
        );
405 18
        $this->executeEnds($ends);
406
407 18
        if (count($this->getErrorAggregator()) > 0) {
408 3
            foreach ($this->getErrorAggregator()->get() as $error) {
409 3
                $this->getJsonApiErrorCollection()->addValidationRelationshipError($error);
410
            }
411 3
            $this->getErrorAggregator()->clear();
412
        }
413
414 18
        return $this;
415
    }
416
417
    /**
418
     * @param int    $index
419
     * @param string $name
420
     * @param mixed  $mightBeRelationship
421
     *
422
     * @return void
423
     *
424
     * @SuppressWarnings(PHPMD.ElseExpression)
425
     */
426 8
    private function validateAsToOneRelationship(int $index, string $name, $mightBeRelationship): void
427
    {
428 8
        if (is_array($mightBeRelationship) === true &&
429 8
            array_key_exists(DI::KEYWORD_DATA, $mightBeRelationship) === true &&
430 8
            ($parsed = $this->parseSingleRelationship($mightBeRelationship[DI::KEYWORD_DATA])) !== false
431
        ) {
432
            // All right we got something. Now pass it to a validation rule.
433 6
            $this->executeBlock($parsed, $index);
434
        } else {
435 2
            $title   = $this->formatMessage(ErrorCodes::INVALID_VALUE);
436 2
            $details = $this->formatMessage(ErrorCodes::INVALID_RELATIONSHIP);
437 2
            $this->getJsonApiErrorCollection()->addRelationshipError($name, $title, $details, $this->getErrorStatus());
438
        }
439
    }
440
441
    /**
442
     * @param int    $index
443
     * @param string $name
444
     * @param mixed  $mightBeRelationship
445
     *
446
     * @return void
447
     *
448
     * @SuppressWarnings(PHPMD.ElseExpression)
449
     */
450 8
    private function validateAsToManyRelationship(int $index, string $name, $mightBeRelationship): void
451
    {
452 8
        $isParsed       = true;
453 8
        $collectedPairs = [];
454 8
        if (is_array($mightBeRelationship) === true &&
455 8
            array_key_exists(DI::KEYWORD_DATA, $mightBeRelationship) === true &&
456 8
            is_array($data = $mightBeRelationship[DI::KEYWORD_DATA]) === true
457
        ) {
458 6
            foreach ($data as $mightTypeAndId) {
459
                // we accept only pairs of type and id (no `null`s are accepted).
460 5
                if (is_array($parsed = $this->parseSingleRelationship($mightTypeAndId)) === true) {
461 4
                    $collectedPairs[] = $parsed;
462
                } else {
463 1
                    $isParsed = false;
464 6
                    break;
465
                }
466
            }
467
        } else {
468 2
            $isParsed = false;
469
        }
470
471 8
        if ($isParsed === true) {
472
            // All right we got something. Now pass it to a validation rule.
473 5
            $this->executeBlock($collectedPairs, $index);
474
        } else {
475 3
            $title   = $this->formatMessage(ErrorCodes::INVALID_VALUE);
476 3
            $details = $this->formatMessage(ErrorCodes::INVALID_RELATIONSHIP);
477 3
            $this->getJsonApiErrorCollection()->addRelationshipError($name, $title, $details, $this->getErrorStatus());
478
        }
479
    }
480
481
    /**
482
     * @param mixed $data
483
     *
484
     * @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...
485
     *
486
     * @SuppressWarnings(PHPMD.ElseExpression)
487
     */
488 7
    private function parseSingleRelationship($data)
489
    {
490 7
        if ($data === null) {
491 2
            $result = null;
492 5
        } elseif (is_array($data) === true &&
493 5
            array_key_exists(DI::KEYWORD_TYPE, $data) === true &&
494 5
            array_key_exists(DI::KEYWORD_ID, $data) === true &&
495 5
            is_scalar($type = $data[DI::KEYWORD_TYPE]) === true &&
496 5
            is_scalar($index = $data[DI::KEYWORD_ID]) === true
497
        ) {
498 4
            $result = [$type => $index];
499
        } else {
500 1
            $result = false;
501
        }
502
503 7
        return $result;
504
    }
505
506
    /**
507
     * Re-initializes internal aggregators for captures, errors, etc.
508
     */
509 19
    private function reInitAggregatorsIfNeeded(): void
510
    {
511 19
        $this->areAggregatorsDirty() === false ?: $this->resetAggregators();
512
    }
513
514
    /**
515
     * @param mixed $input
516
     * @param int   $index
517
     *
518
     * @return void
519
     *
520
     * @SuppressWarnings(PHPMD.StaticAccess)
521
     */
522 17
    private function executeBlock($input, int $index): void
523
    {
524 17
        BlockInterpreter::executeBlock(
525 17
            $input,
526 17
            $index,
527 17
            $this->getBlocks(),
528 17
            $this->getContextStorage(),
529 17
            $this->getCaptureAggregator(),
530 17
            $this->getErrorAggregator()
531
        );
532
    }
533
534
    /**
535
     * @param array $indexes
536
     *
537
     * @return void
538
     *
539
     * @SuppressWarnings(PHPMD.StaticAccess)
540
     */
541 18
    private function executeStarts(array $indexes): void
542
    {
543 18
        BlockInterpreter::executeStarts(
544 18
            $indexes,
545 18
            $this->getBlocks(),
546 18
            $this->getContextStorage(),
547 18
            $this->getErrorAggregator()
548
        );
549
    }
550
551
    /**
552
     * @param array $indexes
553
     *
554
     * @return void
555
     *
556
     * @SuppressWarnings(PHPMD.StaticAccess)
557
     */
558 18
    private function executeEnds(array $indexes): void
559
    {
560 18
        BlockInterpreter::executeEnds(
561 18
            $indexes,
562 18
            $this->getBlocks(),
563 18
            $this->getContextStorage(),
564 18
            $this->getErrorAggregator()
565
        );
566
    }
567
568
    /**
569
     * @param ErrorInterface $error
570
     *
571
     * @return string
572
     */
573 3
    private function getMessage(ErrorInterface $error): string
574
    {
575 3
        $context = $error->getMessageContext();
576 3
        $args    = $context === null ? [] : $context;
577 3
        $message = $this->formatMessage($error->getMessageCode(), $args);
578
579 3
        return $message;
580
    }
581
582
    /**
583
     * @return array
584
     */
585 18
    protected function getIdRule(): array
586
    {
587 18
        return $this->idRule;
588
    }
589
590
    /**
591
     * @return array
592
     */
593 18
    protected function getTypeRule(): array
594
    {
595 18
        return $this->typeRule;
596
    }
597
598
    /**
599
     * @return ContextStorageInterface
600
     */
601 18
    protected function getContextStorage(): ContextStorageInterface
602
    {
603 18
        return $this->contextStorage;
604
    }
605
606
    /**
607
     * @return ContextStorageInterface
608
     */
609 20
    protected function createContextStorage(): ContextStorageInterface
610
    {
611 20
        return new ContextStorage($this->getBlocks(), $this->getContainer());
612
    }
613
614
    /**
615
     * @return JsonApiErrorCollection
616
     */
617 19
    protected function getJsonApiErrorCollection(): JsonApiErrorCollection
618
    {
619 19
        return $this->jsonApiErrors;
620
    }
621
622
    /**
623
     * @return JsonApiErrorCollection
624
     */
625 20
    protected function createJsonApiErrors(): JsonApiErrorCollection
626
    {
627 20
        return new JsonApiErrorCollection($this->getContainer(), $this->getErrorStatus());
628
    }
629
630
    /**
631
     * @return int
632
     */
633 20
    protected function getErrorStatus(): int
634
    {
635 20
        return $this->errorStatus;
636
    }
637
638
    /**
639
     * @return bool
640
     */
641 1
    protected function isIgnoreUnknowns(): bool
642
    {
643 1
        return $this->isIgnoreUnknowns;
644
    }
645
646
    /**
647
     * @return self
648
     */
649 1
    protected function enableIgnoreUnknowns(): self
650
    {
651 1
        $this->isIgnoreUnknowns = true;
652
653 1
        return $this;
654
    }
655
656
    /**
657
     * @return self
658
     */
659 20
    protected function disableIgnoreUnknowns(): self
660
    {
661 20
        $this->isIgnoreUnknowns = false;
662
663 20
        return $this;
664
    }
665
666
    /**
667
     * @param array $rules
668
     *
669
     * @return self
670
     */
671 20
    private function setAttributeRules(array $rules): self
672
    {
673 20
        assert($this->debugCheckIndexesExist($rules));
674
675 20
        $this->attributeRules = $rules;
676
677 20
        return $this;
678
    }
679
680
    /**
681
     * @param array $rules
682
     *
683
     * @return self
684
     */
685 20
    private function setToOneIndexes(array $rules): self
686
    {
687 20
        assert($this->debugCheckIndexesExist($rules));
688
689 20
        $this->toOneRules = $rules;
690
691 20
        return $this;
692
    }
693
694
    /**
695
     * @param array $rules
696
     *
697
     * @return self
698
     */
699 20
    private function setToManyIndexes(array $rules): self
700
    {
701 20
        assert($this->debugCheckIndexesExist($rules));
702
703 20
        $this->toManyRules = $rules;
704
705 20
        return $this;
706
    }
707
708
    /**
709
     * @return int[]
710
     */
711 18
    protected function getAttributeRules(): array
712
    {
713 18
        return $this->attributeRules;
714
    }
715
716
    /**
717
     * @return int[]
718
     */
719 18
    protected function getToOneRules(): array
720
    {
721 18
        return $this->toOneRules;
722
    }
723
724
    /**
725
     * @return int[]
726
     */
727 18
    protected function getToManyRules(): array
728
    {
729 18
        return $this->toManyRules;
730
    }
731
732
    /**
733
     * @return array
734
     */
735 20
    private function getBlocks(): array
736
    {
737 20
        return $this->blocks;
738
    }
739
740
    /**
741
     * @return FormatterInterface
742
     */
743 9
    protected function getMessageFormatter(): FormatterInterface
744
    {
745 9
        if ($this->messageFormatter === null) {
746
            /** @var FormatterFactoryInterface $factory */
747 9
            $factory                = $this->getContainer()->get(FormatterFactoryInterface::class);
748 9
            $this->messageFormatter = $factory->createFormatter(static::RESOURCES_NAMESPACE);
749
        }
750
751 9
        return $this->messageFormatter;
752
    }
753
754
    /**
755
     * @param string $name
756
     *
757
     * @return int|null
758
     *
759
     * @SuppressWarnings(PHPMD.StaticAccess)
760
     */
761 12
    private function getAttributeIndex(string $name): ?int
762
    {
763 12
        $indexes = JsonApiRuleSerializer::getRulesIndexes($this->getAttributeRules());
764 12
        $index   = $indexes[$name] ?? null;
765
766 12
        return $index;
767
    }
768
769
    /**
770
     * @param int   $messageId
771
     * @param array $args
772
     *
773
     * @return string
774
     */
775 9
    private function formatMessage(int $messageId, array $args = []): string
776
    {
777 9
        $message = $this->getMessageFormatter()->formatMessage($messageId, $args);
778
779 9
        return $message;
780
    }
781
782
    /**
783
     * @param array $rules
784
     *
785
     * @return bool
786
     *
787
     * @SuppressWarnings(PHPMD.StaticAccess)
788
     */
789 20
    private function debugCheckIndexesExist(array $rules): bool
790
    {
791 20
        $allOk = true;
792
793 20
        $indexes = array_merge(
794 20
            JsonApiRuleSerializer::getRulesIndexes($rules),
795 20
            JsonApiRuleSerializer::getRulesStartIndexes($rules),
796 20
            JsonApiRuleSerializer::getRulesEndIndexes($rules)
797
        );
798
799 20
        foreach ($indexes as $index) {
800 16
            $allOk = $allOk && is_int($index) && JsonApiRuleSerializer::isRuleExist($index, $this->getBlocks());
801
        }
802
803 20
        return $allOk;
804
    }
805
}
806