Passed
Pull Request — master (#564)
by Sergei
02:52
created

NestedHandler::prepareRules()   A

Complexity

Conditions 6
Paths 10

Size

Total Lines 20
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 9
CRAP Score 6

Importance

Changes 0
Metric Value
cc 6
eloc 12
nc 10
nop 3
dl 0
loc 20
ccs 9
cts 9
cp 1
crap 6
rs 9.2222
c 0
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Yiisoft\Validator\Rule;
6
7
use Yiisoft\Arrays\ArrayHelper;
8
use Yiisoft\Strings\StringHelper;
9
use Yiisoft\Validator\DataSet\ObjectDataSet;
10
use Yiisoft\Validator\Exception\UnexpectedRuleException;
11
use Yiisoft\Validator\Result;
12
use Yiisoft\Validator\RuleHandlerInterface;
13
use Yiisoft\Validator\RuleInterface;
14
use Yiisoft\Validator\ValidationContext;
15
16
use function is_array;
17
use function is_int;
18
use function is_object;
19
20
/**
21
 * A handler for {@see Nested} rule. Validates nested structures.
22
 */
23
final class NestedHandler implements RuleHandlerInterface
24
{
25
    public function validate(mixed $value, object $rule, ValidationContext $context): Result
26
    {
27
        if (!$rule instanceof Nested) {
28
            throw new UnexpectedRuleException(Nested::class, $rule);
29
        }
30
31
        /** @var mixed $value */
32
        $value = $context->getParameter(ValidationContext::PARAMETER_VALUE_AS_ARRAY) ?? $value;
33
34
        if ($rule->getRules() === null) {
35
            if (!is_object($value)) {
36
                return (new Result())->addError($rule->getNoRulesWithNoObjectMessage(), [
37
                    'attribute' => $context->getTranslatedAttribute(),
38
                    'type' => get_debug_type($value),
39
                ]);
40
            }
41
42
            $dataSet = new ObjectDataSet($value, $rule->getValidatedObjectPropertyVisibility());
43
44
            return $context->validate($dataSet);
45
        }
46 53
47
        if (is_array($value)) {
48 53
            $data = $value;
49 1
        } elseif (is_object($value)) {
50
            $data = (new ObjectDataSet($value, $rule->getValidatedObjectPropertyVisibility()))->getData();
51
            if ($data === null) {
52 52
                return (new Result())->addError($rule->getIncorrectDataSetTypeMessage(), [
53
                    'type' => get_debug_type($data),
54 52
                ]);
55 10
            }
56 5
        } else {
57 5
            return (new Result())->addError($rule->getIncorrectInputMessage(), [
58 5
                'attribute' => $context->getTranslatedAttribute(),
59
                'type' => get_debug_type($value),
60
            ]);
61
        }
62 5
63
        $preparedRules = [];
64 5
        $this->prepareRules($rule->getRules(), $preparedRules);
65
66
        $compoundResult = new Result();
67 42
68 33
        foreach ($preparedRules as $valuePath => $rules) {
69 9
            if ($rule->isPropertyPathRequired() && !ArrayHelper::pathExists($data, $valuePath)) {
0 ignored issues
show
Bug introduced by
It seems like $data can also be of type null; however, parameter $array of Yiisoft\Arrays\ArrayHelper::pathExists() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

69
            if ($rule->isPropertyPathRequired() && !ArrayHelper::pathExists(/** @scrutinizer ignore-type */ $data, $valuePath)) {
Loading history...
70
                if (is_int($valuePath)) {
71 4
                    $valuePathList = [$valuePath];
72 4
                } else {
73 3
                    /** @var list<string> $valuePathList */
74 4
                    $valuePathList = StringHelper::parsePath($valuePath);
75
                }
76
77
                $compoundResult->addError(
78 5
                    $rule->getNoPropertyPathMessage(),
79 5
                    [
80 5
                        'path' => $valuePath,
81
                        'attribute' => $context->getTranslatedAttribute(),
82
                    ],
83
                    $valuePathList,
84 34
                );
85
86 34
                continue;
87 33
            }
88 4
89 1
            /** @var mixed $validatedValue */
90
            $validatedValue = ArrayHelper::getValueByPath($data, $valuePath);
0 ignored issues
show
Bug introduced by
It seems like $data can also be of type null; however, parameter $array of Yiisoft\Arrays\ArrayHelper::getValueByPath() does only seem to accept array|object, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

90
            $validatedValue = ArrayHelper::getValueByPath(/** @scrutinizer ignore-type */ $data, $valuePath);
Loading history...
91
92 3
            $itemResult = $context->validate($validatedValue, $rules);
93
            if ($itemResult->isValid()) {
94
                continue;
95 4
            }
96 4
97
            foreach ($itemResult->getErrors() as $error) {
98
                if (is_int($valuePath)) {
99 4
                    $valuePathList = [$valuePath];
100
                } else {
101
                    /** @var list<string> $valuePathList */
102
                    $valuePathList = StringHelper::parsePath($valuePath);
103
                }
104 4
105
                if (!empty($valuePathList)) {
106
                    array_push($valuePathList, ...$error->getValuePath());
0 ignored issues
show
Bug introduced by
It seems like $valuePathList can also be of type Yiisoft\Validator\Rule\list; however, parameter $array of array_push() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

106
                    array_push(/** @scrutinizer ignore-type */ $valuePathList, ...$error->getValuePath());
Loading history...
107
                }
108 29
109 29
                $compoundResult->addError($error->getMessage(), $error->getParameters(), $valuePathList);
110
            }
111 29
        }
112 29
113 14
        return $compoundResult;
114
    }
115
116 20
    /**
117 20
     * @psalm-param iterable<iterable<RuleInterface>|RuleInterface> $rawRules
118 20
     *
119 2
     * @param RuleInterface[] $result
120
     */
121
    public function prepareRules(iterable $rawRules, array &$result, ?string $baseValuePath = null): void
122 18
    {
123
        /** @var int|string $valuePath */
124
        foreach ($rawRules as $valuePath => $rules) {
125 20
            if (is_int($valuePath)) {
126 20
                $key = $baseValuePath;
127
            } else {
128
                $key = ($baseValuePath !== null ? $baseValuePath . '.' : '') . $valuePath;
129 20
            }
130
131 20
            if (is_iterable($rules)) {
132
                $this->prepareRules($rules, $result, $key);
133
                continue;
134 34
            }
135 20
136 20
            if ($key === null) {
137
                $result[] = $rules;
138
            } else {
139
                /** @psalm-suppress UndefinedInterfaceMethod */
140 34
                $result[$key][] = $rules;
141
            }
142
        }
143
    }
144
}
145