Completed
Push — master ( 6173d9...210649 )
by Neomerx
04:26
created

DataParser::parseRelationship()   B

Complexity

Conditions 8
Paths 10

Size

Total Lines 40

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 26
CRAP Score 8

Importance

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