Completed
Push — master ( 29d040...599906 )
by Neomerx
04:36
created

Validator::getContainer()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
dl 0
loc 4
ccs 2
cts 2
cp 1
rs 10
c 0
b 0
f 0
cc 1
eloc 2
nc 1
nop 0
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
    public function __construct(
134
        string $name,
135
        array $data,
136
        ContainerInterface $container,
137 18
        int $errorStatus = JsonApiResponse::HTTP_UNPROCESSABLE_ENTITY
138
    ) {
139
        $this->setContainer($container);
140
141
        $ruleSet           = JsonApiRuleSerializer::extractRuleSet($name, $data);
142
        $this->blocks      = JsonApiRuleSerializer::extractBlocks($data);
143 18
        $this->idRule      = JsonApiRuleSerializer::getIdRule($ruleSet);
144 18
        $this->typeRule    = JsonApiRuleSerializer::getTypeRule($ruleSet);
145 18
        $this->errorStatus = $errorStatus;
146 18
147 18
        $this
148 18
            ->setAttributeRules(JsonApiRuleSerializer::getAttributeRules($ruleSet))
149
            ->setToOneIndexes(JsonApiRuleSerializer::getToOneRules($ruleSet))
150
            ->setToManyIndexes(JsonApiRuleSerializer::getToManyRules($ruleSet))
151 18
            ->disableIgnoreUnknowns();
152 18
153 18
        parent::__construct();
154 18
    }
155
156 18
    /**
157
     * @inheritdoc
158
     */
159
    public function assert($jsonData): JsonApiValidatorInterface
160
    {
161
        if ($this->validate($jsonData) === false) {
162 12
            throw new JsonApiException($this->getJsonApiErrorCollection(), $this->getErrorStatus());
163
        }
164 12
165 5
        return $this;
166
    }
167
168 7
    /**
169
     * @inheritdoc
170
     */
171
    public function validate($input): bool
172
    {
173
        $this->reInitAggregatorsIfNeeded();
174 16
175
        if (is_array($input) === true) {
176 16
            $this
177
                ->validateType($input)
178
                ->validateId($input)
179 16
                ->validateAttributes($input)
180 16
                ->validateRelationships($input);
181 16
        } else {
182 16
            $title   = $this->formatMessage(ErrorCodes::INVALID_VALUE);
183
            $details = $this->formatMessage(ErrorCodes::INVALID_VALUE);
184 16
            $this->getJsonApiErrorCollection()->addDataError($title, $details, $this->getErrorStatus());
185
        }
186 16
187
        $hasNoErrors = $this->getJsonApiErrorCollection()->count() <= 0;
188
189
        return $hasNoErrors;
190
    }
191
192 4
    /**
193
     * @inheritdoc
194 4
     */
195
    public function getJsonApiErrors(): array
196
    {
197
        return $this->getJsonApiErrorCollection()->getArrayCopy();
198
    }
199
200 10
    /**
201
     * @inheritdoc
202 10
     */
203
    public function getJsonApiCaptures(): array
204
    {
205
        return $this->getCaptures()->get();
206
    }
207
208 18
    /**
209
     * @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...
210 18
     */
211
    protected function resetAggregators(): BaseValidator
212 18
    {
213 18
        $self = parent::resetAggregators();
214
215 18
        $this->jsonApiErrors  = $this->createJsonApiErrors();
216
        $this->contextStorage = $this->createContextStorage();
217
218
        return $self;
219
    }
220
221
    /**
222
     * @param array $jsonData
223
     *
224
     * @return self
225
     *
226 16
     * @SuppressWarnings(PHPMD.StaticAccess)
227
     * @SuppressWarnings(PHPMD.ElseExpression)
228
     */
229 16
    private function validateType(array $jsonData): self
230 16
    {
231
        // execute start(s)
232 16
        $starts = JsonApiRuleSerializer::getRuleStartIndexes($this->getTypeRule());
233 16
        $this->executeStarts($starts);
234
235
        if (array_key_exists(DI::KEYWORD_DATA, $jsonData) === true &&
236 15
            array_key_exists(DI::KEYWORD_TYPE, $data = $jsonData[DI::KEYWORD_DATA]) === true
237 15
        ) {
238
            // execute main validation block(s)
239 1
            $index = JsonApiRuleSerializer::getRuleIndex($this->getTypeRule());
240 1
            $this->executeBlock($data[DI::KEYWORD_TYPE], $index);
241 1
        } else {
242
            $title   = $this->formatMessage(ErrorCodes::INVALID_VALUE);
243
            $details = $this->formatMessage(ErrorCodes::TYPE_MISSING);
244
            $this->getJsonApiErrorCollection()->addDataTypeError($title, $details, $this->getErrorStatus());
245 16
        }
246 16
247
        // execute end(s)
248 16
        $ends = JsonApiRuleSerializer::getRuleEndIndexes($this->getTypeRule());
249 1
        $this->executeEnds($ends);
250 1
251 1 View Code Duplication
        if (count($this->getErrors()) > 0) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
252 1
            $title = $this->formatMessage(ErrorCodes::INVALID_VALUE);
253
            foreach ($this->getErrors()->get() as $error) {
254 1
                $this->getJsonApiErrorCollection()
255
                    ->addDataTypeError($title, $this->getMessage($error), $this->getErrorStatus());
256
            }
257 16
            $this->getErrors()->clear();
258
        }
259
260
        return $this;
261
    }
262
263
    /**
264
     * @param array $jsonData
265
     *
266
     * @return self
267 16
     *
268
     * @SuppressWarnings(PHPMD.StaticAccess)
269
     */
270 16
    private function validateId(array $jsonData): self
271 16
    {
272
        // execute start(s)
273
        $starts = JsonApiRuleSerializer::getRuleStartIndexes($this->getIdRule());
274 16
        $this->executeStarts($starts);
275 16
276
        // execute main validation block(s)
277 14
        if (array_key_exists(DI::KEYWORD_DATA, $jsonData) === true &&
278 14
            array_key_exists(DI::KEYWORD_ID, $data = $jsonData[DI::KEYWORD_DATA]) === true
279
        ) {
280
            $index = JsonApiRuleSerializer::getRuleIndex($this->getIdRule());
281
            $this->executeBlock($data[DI::KEYWORD_ID], $index);
282 16
        }
283 16
284
        // execute end(s)
285 16
        $ends = JsonApiRuleSerializer::getRuleEndIndexes($this->getIdRule());
286 3
        $this->executeEnds($ends);
287 3
288 3 View Code Duplication
        if (count($this->getErrors()) > 0) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
289 3
            $title = $this->formatMessage(ErrorCodes::INVALID_VALUE);
290
            foreach ($this->getErrors()->get() as $error) {
291 3
                $this->getJsonApiErrorCollection()
292
                    ->addDataIdError($title, $this->getMessage($error), $this->getErrorStatus());
293
            }
294 16
            $this->getErrors()->clear();
295
        }
296
297
        return $this;
298
    }
299
300
    /**
301
     * @param array $jsonData
302
     *
303
     * @return self
304
     *
305 16
     * @SuppressWarnings(PHPMD.StaticAccess)
306
     * @SuppressWarnings(PHPMD.ElseExpression)
307
     */
308 16
    private function validateAttributes(array $jsonData): self
309 16
    {
310
        // execute start(s)
311 16
        $starts = JsonApiRuleSerializer::getRulesStartIndexes($this->getAttributeRules());
312 16
        $this->executeStarts($starts);
313
314 15
        if (array_key_exists(DI::KEYWORD_DATA, $jsonData) === true &&
315 2
            array_key_exists(DI::KEYWORD_ATTRIBUTES, $data = $jsonData[DI::KEYWORD_DATA]) === true
316 2
        ) {
317 2
            if (is_array($attributes = $data[DI::KEYWORD_ATTRIBUTES]) === false) {
318
                $title   = $this->formatMessage(ErrorCodes::INVALID_VALUE);
319
                $details = $this->formatMessage(ErrorCodes::INVALID_ATTRIBUTES);
320 13
                $this->getJsonApiErrorCollection()->addAttributesError($title, $details, $this->getErrorStatus());
321 10
            } else {
322 9
                // execute main validation block(s)
323 1
                foreach ($attributes as $name => $value) {
324 1
                    if ($this->hasAttributeIndex($name) === true) {
325 1
                        $this->executeBlock($value, $this->getAttributeIndex($name));
326 1 View Code Duplication
                    } elseif ($this->isIgnoreUnknowns() === false) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
327 10
                        $title   = $this->formatMessage(ErrorCodes::INVALID_VALUE);
328
                        $details = $this->formatMessage(ErrorCodes::UNKNOWN_ATTRIBUTE);
329
                        $status  = $this->getErrorStatus();
330
                        $this->getJsonApiErrorCollection()->addDataAttributeError($name, $title, $details, $status);
331
                    }
332
                }
333
            }
334 16
        }
335 16
336
        // execute end(s)
337 16
        $ends = JsonApiRuleSerializer::getRulesEndIndexes($this->getAttributeRules());
338 2
        $this->executeEnds($ends);
339 2
340
        if (count($this->getErrors()) > 0) {
341 2
            foreach ($this->getErrors()->get() as $error) {
342
                $this->getJsonApiErrorCollection()->addValidationAttributeError($error);
343
            }
344 16
            $this->getErrors()->clear();
345
        }
346
347
        return $this;
348
    }
349
350
    /**
351
     * @param array $jsonData
352
     *
353
     * @return self
354
     *
355 16
     * @SuppressWarnings(PHPMD.StaticAccess)
356
     * @SuppressWarnings(PHPMD.ElseExpression)
357
     */
358 16
    private function validateRelationships(array $jsonData): self
359 16
    {
360 16
        // execute start(s)
361
        $starts = array_merge(
362 16
            JsonApiRuleSerializer::getRulesStartIndexes($this->getToOneRules()),
363
            JsonApiRuleSerializer::getRulesStartIndexes($this->getToManyRules())
364 16
        );
365 16
        $this->executeStarts($starts);
366
367 10
        if (array_key_exists(DI::KEYWORD_DATA, $jsonData) === true &&
368 1
            array_key_exists(DI::KEYWORD_RELATIONSHIPS, $data = $jsonData[DI::KEYWORD_DATA]) === true
369 1
        ) {
370 1
            if (is_array($relationships = $data[DI::KEYWORD_RELATIONSHIPS]) === false) {
371
                $title   = $this->formatMessage(ErrorCodes::INVALID_VALUE);
372
                $details = $this->formatMessage(ErrorCodes::INVALID_RELATIONSHIP_TYPE);
373 9
                $this->getJsonApiErrorCollection()->addRelationshipsError($title, $details, $this->getErrorStatus());
374 9
            } else {
375
                // ok we got to something that could be null or a valid relationship
376 9
                $toOneIndexes  = JsonApiRuleSerializer::getRulesIndexes($this->getToOneRules());
377 9
                $toManyIndexes = JsonApiRuleSerializer::getRulesIndexes($this->getToManyRules());
378
379 8
                foreach ($relationships as $name => $relationship) {
380 9
                    if (array_key_exists($name, $toOneIndexes) === true) {
381
                        // it might be to1 relationship
382 8
                        $this->validateAsToOneRelationship($toOneIndexes[$name], $name, $relationship);
383
                    } elseif (array_key_exists($name, $toManyIndexes) === true) {
384
                        // it might be toMany relationship
385 1
                        $this->validateAsToManyRelationship($toManyIndexes[$name], $name, $relationship);
386 1 View Code Duplication
                    } else {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
387 1
                        // unknown relationship
388 9
                        $title   = $this->formatMessage(ErrorCodes::INVALID_VALUE);
389
                        $details = $this->formatMessage(ErrorCodes::UNKNOWN_RELATIONSHIP);
390
                        $status  = $this->getErrorStatus();
391
                        $this->getJsonApiErrorCollection()->addRelationshipError($name, $title, $details, $status);
392
                    }
393
                }
394
            }
395 16
        }
396 16
397 16
        // execute end(s)
398
        $ends = array_merge(
399 16
            JsonApiRuleSerializer::getRulesEndIndexes($this->getToOneRules()),
400
            JsonApiRuleSerializer::getRulesEndIndexes($this->getToManyRules())
401 16
        );
402 3
        $this->executeEnds($ends);
403 3
404
        if (count($this->getErrors()) > 0) {
405 3
            foreach ($this->getErrors()->get() as $error) {
406
                $this->getJsonApiErrorCollection()->addValidationRelationshipError($error);
407
            }
408 16
            $this->getErrors()->clear();
409
        }
410
411
        return $this;
412
    }
413
414
    /**
415
     * @param int    $index
416
     * @param string $name
417
     * @param mixed  $mightBeRelationship
418
     *
419
     * @return void
420 8
     *
421
     * @SuppressWarnings(PHPMD.ElseExpression)
422 8
     */
423 8
    private function validateAsToOneRelationship(int $index, string $name, $mightBeRelationship): void
424 8
    {
425
        if (is_array($mightBeRelationship) === true &&
426
            array_key_exists(DI::KEYWORD_DATA, $mightBeRelationship) === true &&
427 6
            ($parsed = $this->parseSingleRelationship($mightBeRelationship[DI::KEYWORD_DATA])) !== false
428
        ) {
429 2
            // All right we got something. Now pass it to a validation rule.
430 2
            $this->executeBlock($parsed, $index);
431 2
        } else {
432
            $title   = $this->formatMessage(ErrorCodes::INVALID_VALUE);
433
            $details = $this->formatMessage(ErrorCodes::INVALID_RELATIONSHIP);
434
            $this->getJsonApiErrorCollection()->addRelationshipError($name, $title, $details, $this->getErrorStatus());
435
        }
436
    }
437
438
    /**
439
     * @param int    $index
440
     * @param string $name
441
     * @param mixed  $mightBeRelationship
442
     *
443
     * @return void
444 8
     *
445
     * @SuppressWarnings(PHPMD.ElseExpression)
446 8
     */
447 8
    private function validateAsToManyRelationship(int $index, string $name, $mightBeRelationship): void
448 8
    {
449 8
        $isParsed       = true;
450 8
        $collectedPairs = [];
451
        if (is_array($mightBeRelationship) === true &&
452 6
            array_key_exists(DI::KEYWORD_DATA, $mightBeRelationship) === true &&
453
            is_array($data = $mightBeRelationship[DI::KEYWORD_DATA]) === true
454 5
        ) {
455 4
            foreach ($data as $mightTypeAndId) {
456
                // we accept only pairs of type and id (no `null`s are accepted).
457 1
                if (is_array($parsed = $this->parseSingleRelationship($mightTypeAndId)) === true) {
458 6
                    $collectedPairs[] = $parsed;
459
                } else {
460
                    $isParsed = false;
461
                    break;
462 2
                }
463
            }
464
        } else {
465 8
            $isParsed = false;
466
        }
467 5
468 View Code Duplication
        if ($isParsed === true) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
469 3
            // All right we got something. Now pass it to a validation rule.
470 3
            $this->executeBlock($collectedPairs, $index);
471 3
        } else {
472
            $title   = $this->formatMessage(ErrorCodes::INVALID_VALUE);
473
            $details = $this->formatMessage(ErrorCodes::INVALID_RELATIONSHIP);
474
            $this->getJsonApiErrorCollection()->addRelationshipError($name, $title, $details, $this->getErrorStatus());
475
        }
476
    }
477
478
    /**
479
     * @param mixed $data
480
     *
481
     * @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...
482 7
     *
483
     * @SuppressWarnings(PHPMD.ElseExpression)
484 7
     */
485 2
    private function parseSingleRelationship($data)
486 5
    {
487 5
        if ($data === null) {
488 5
            $result = null;
489 5
        } elseif (is_array($data) === true &&
490 5
            array_key_exists(DI::KEYWORD_TYPE, $data) === true &&
491
            array_key_exists(DI::KEYWORD_ID, $data) === true &&
492 4
            is_scalar($type = $data[DI::KEYWORD_TYPE]) === true &&
493
            is_scalar($index = $data[DI::KEYWORD_ID]) === true
494 1
        ) {
495
            $result = [$type => $index];
496
        } else {
497 7
            $result = false;
498
        }
499
500
        return $result;
501
    }
502
503 16
    /**
504
     * Re-initializes internal aggregators for captures, errors, etc.
505 16
     */
506
    private function reInitAggregatorsIfNeeded(): void
507
    {
508
        $this->areAggregatorsDirty() === false ?: $this->resetAggregators();
509
    }
510
511
    /**
512
     * @param mixed $input
513
     * @param int   $index
514
     *
515
     * @return void
516 15
     *
517
     * @SuppressWarnings(PHPMD.StaticAccess)
518 15
     */
519 15
    private function executeBlock($input, int $index): void
520 15
    {
521 15
        BlockInterpreter::executeBlock(
522 15
            $input,
523 15
            $index,
524 15
            $this->getBlocks(),
525
            $this->getContextStorage(),
526
            $this->getCaptures(),
527
            $this->getErrors()
528
        );
529
    }
530
531
    /**
532
     * @param array $indexes
533
     *
534
     * @return void
535 16
     *
536
     * @SuppressWarnings(PHPMD.StaticAccess)
537 16
     */
538
    private function executeStarts(array $indexes): void
539
    {
540
        BlockInterpreter::executeStarts($indexes, $this->getBlocks(), $this->getContextStorage(), $this->getErrors());
541
    }
542
543
    /**
544
     * @param array $indexes
545
     *
546
     * @return void
547 16
     *
548
     * @SuppressWarnings(PHPMD.StaticAccess)
549 16
     */
550
    private function executeEnds(array $indexes): void
551
    {
552
        BlockInterpreter::executeEnds($indexes, $this->getBlocks(), $this->getContextStorage(), $this->getErrors());
553
    }
554
555
    /**
556
     * @param ErrorInterface $error
557 3
     *
558
     * @return string
559 3
     */
560 3 View Code Duplication
    private function getMessage(ErrorInterface $error): string
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
561 3
    {
562
        $context = $error->getMessageContext();
563 3
        $args    = $context === null ? [] : $context;
564
        $message = $this->formatMessage($error->getMessageCode(), $args);
565
566
        return $message;
567
    }
568
569 16
    /**
570
     * @return array
571 16
     */
572
    protected function getIdRule(): array
573
    {
574
        return $this->idRule;
575
    }
576
577 16
    /**
578
     * @return array
579 16
     */
580
    protected function getTypeRule(): array
581
    {
582
        return $this->typeRule;
583
    }
584
585 16
    /**
586
     * @return ContextStorageInterface
587 16
     */
588
    protected function getContextStorage(): ContextStorageInterface
589
    {
590
        return $this->contextStorage;
591
    }
592
593 18
    /**
594
     * @return ContextStorageInterface
595 18
     */
596
    protected function createContextStorage(): ContextStorageInterface
597
    {
598
        return new ContextStorage($this->getContainer(), $this->getBlocks());
599
    }
600
601 16
    /**
602
     * @return JsonApiErrorCollection
603 16
     */
604
    protected function getJsonApiErrorCollection(): JsonApiErrorCollection
605
    {
606
        return $this->jsonApiErrors;
607
    }
608
609 18
    /**
610
     * @return JsonApiErrorCollection
611 18
     */
612
    protected function createJsonApiErrors(): JsonApiErrorCollection
613
    {
614
        return new JsonApiErrorCollection($this->getContainer(), $this->getErrorStatus());
615
    }
616
617 18
    /**
618
     * @return int
619 18
     */
620
    protected function getErrorStatus(): int
621
    {
622
        return $this->errorStatus;
623
    }
624
625 18
    /**
626
     * @return bool
627 18
     */
628
    protected function isIgnoreUnknowns(): bool
629
    {
630
        return $this->isIgnoreUnknowns;
631
    }
632
633 1
    /**
634
     * @return self
635 1
     */
636
    protected function enableIgnoreUnknowns(): self
637
    {
638
        $this->isIgnoreUnknowns = true;
639
640
        return $this;
641 1
    }
642
643 1
    /**
644
     * @return self
645 1
     */
646
    protected function disableIgnoreUnknowns(): self
647
    {
648
        $this->isIgnoreUnknowns = false;
649
650
        return $this;
651 18
    }
652
653 18
    /**
654
     * @param array $rules
655 18
     *
656
     * @return self
657
     */
658
    private function setAttributeRules(array $rules): self
659
    {
660
        assert($this->debugCheckIndexesExist($rules));
661
662
        $this->attributeRules = $rules;
663 18
664
        return $this;
665 18
    }
666
667 18
    /**
668
     * @param array $rules
669 18
     *
670
     * @return self
671
     */
672
    private function setToOneIndexes(array $rules): self
673
    {
674
        assert($this->debugCheckIndexesExist($rules));
675
676
        $this->toOneRules = $rules;
677 18
678
        return $this;
679 18
    }
680
681 18
    /**
682
     * @param array $rules
683 18
     *
684
     * @return self
685
     */
686
    private function setToManyIndexes(array $rules): self
687
    {
688
        assert($this->debugCheckIndexesExist($rules));
689
690
        $this->toManyRules = $rules;
691 18
692
        return $this;
693 18
    }
694
695 18
    /**
696
     * @return int[]
697 18
     */
698
    protected function getAttributeRules(): array
699
    {
700
        return $this->attributeRules;
701
    }
702
703 16
    /**
704
     * @return int[]
705 16
     */
706
    protected function getToOneRules(): array
707
    {
708
        return $this->toOneRules;
709
    }
710
711 16
    /**
712
     * @return int[]
713 16
     */
714
    protected function getToManyRules(): array
715
    {
716
        return $this->toManyRules;
717
    }
718
719 16
    /**
720
     * @return array
721 16
     */
722
    private function getBlocks(): array
723
    {
724
        return $this->blocks;
725
    }
726
727 18
    /**
728
     * @return FormatterInterface
729 18
     */
730
    protected function getMessageFormatter(): FormatterInterface
731
    {
732
        if ($this->messageFormatter === null) {
733
            /** @var FormatterFactoryInterface $factory */
734
            $factory                = $this->getContainer()->get(FormatterFactoryInterface::class);
735 8
            $this->messageFormatter = $factory->createFormatter(static::RESOURCES_NAMESPACE);
736
        }
737 8
738
        return $this->messageFormatter;
739 8
    }
740 8
741
    /**
742
     * @param string $name
743 8
     *
744
     * @return int
745
     *
746
     * @SuppressWarnings(PHPMD.StaticAccess)
747
     */
748
    private function getAttributeIndex(string $name): int
749
    {
750
        $indexes = JsonApiRuleSerializer::getRulesIndexes($this->getAttributeRules());
751
        $index   = $indexes[$name];
752
753 9
        return $index;
754
    }
755 9
756 9
    /**
757
     * @param string $name
758 9
     *
759
     * @return bool
760
     *
761
     * @SuppressWarnings(PHPMD.StaticAccess)
762
     */
763
    private function hasAttributeIndex(string $name): bool
764
    {
765
        $indexes      = JsonApiRuleSerializer::getRulesIndexes($this->getAttributeRules());
766
        $hasAttribute = array_key_exists($name, $indexes);
767
768 10
        return $hasAttribute;
769
    }
770 10
771 10
    /**
772
     * @param int   $messageId
773 10
     * @param array $args
774
     *
775
     * @return string
776
     */
777
    private function formatMessage(int $messageId, array $args = []): string
778
    {
779
        $message = $this->getMessageFormatter()->formatMessage($messageId, $args);
780
781
        return $message;
782 8
    }
783
784 8
    /**
785
     * @param array $rules
786 8
     *
787
     * @return bool
788
     *
789
     * @SuppressWarnings(PHPMD.StaticAccess)
790
     */
791
    private function debugCheckIndexesExist(array $rules): bool
792
    {
793
        $allOk = true;
794
795
        $indexes = array_merge(
796 18
            JsonApiRuleSerializer::getRulesIndexes($rules),
797
            JsonApiRuleSerializer::getRulesStartIndexes($rules),
798 18
            JsonApiRuleSerializer::getRulesEndIndexes($rules)
799
        );
800 18
801 18
        foreach ($indexes as $index) {
802 18
            $allOk = $allOk && is_int($index) && JsonApiRuleSerializer::isRuleExist($index, $this->getBlocks());
803 18
        }
804
805
        return $allOk;
806 18
    }
807
}
808