Passed
Push — master ( 5c2907...37dc07 )
by Sergei
24:35 queued 21:56
created

NestedHandler::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 2
Code Lines 0

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 1
CRAP Score 1

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 0
c 1
b 0
f 0
dl 0
loc 2
ccs 1
cts 1
cp 1
rs 10
cc 1
nc 1
nop 1
crap 1
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Yiisoft\Validator\Rule;
6
7
use InvalidArgumentException;
8
use Yiisoft\Arrays\ArrayHelper;
9
use Yiisoft\Strings\StringHelper;
10
use Yiisoft\Validator\DataSet\ObjectDataSet;
11
use Yiisoft\Validator\Exception\UnexpectedRuleException;
12
use Yiisoft\Validator\Result;
13
use Yiisoft\Validator\RuleHandlerInterface;
14
use Yiisoft\Validator\ValidationContext;
15
16
use function is_array;
17
use function is_int;
18
use function is_object;
19
20
/**
21
 * Can be used for validation of nested structures.
22
 *
23
 * For example, we have an inbound request with the following structure:
24
 *
25
 * ```php
26
 * $request = [
27
 *     'author' => [
28
 *         'name' => 'Dmitry',
29
 *         'age' => 18,
30
 *     ],
31
 * ];
32
 * ```
33
 *
34
 * So to make validation we can configure it like this:
35
 *
36
 * ```php
37
 * $rule = new Nested([
38
 *     'author' => new Nested([
39
 *         'name' => [new HasLength(min: 3)],
40
 *         'age' => [new Number(min: 18)],
41
 *     )];
42
 * ]);
43
 * ```
44
 */
45
final class NestedHandler implements RuleHandlerInterface
46
{
47 39
    public function validate(mixed $value, object $rule, ValidationContext $context): Result
48
    {
49 39
        if (!$rule instanceof Nested) {
50 1
            throw new UnexpectedRuleException(Nested::class, $rule);
51
        }
52
53 38
        if ($rule->getRules() === null) {
54 8
            if (!is_object($value)) {
55 3
                throw new InvalidArgumentException(
56 3
                    sprintf(
57
                        'Nested rule without rules could be used for objects only. %s given.',
58 3
                        get_debug_type($value)
59
                    )
60
                );
61
            }
62
63 5
            $dataSet = new ObjectDataSet($value, $rule->getPropertyVisibility());
64
65 5
            return $context->getValidator()->validate($dataSet);
66
        }
67
68 30
        if (is_array($value)) {
69 27
            $data = $value;
70 3
        } elseif (is_object($value)) {
71 1
            $data = (new ObjectDataSet($value, $rule->getPropertyVisibility()))->getData();
72
        } else {
73 2
            $message = sprintf(
74
                'Value should be an array or an object. %s given.',
75 2
                get_debug_type($value)
76
            );
77 2
            return (new Result())->addError(
78
                $message,
79
                [
80 2
                    'attribute' => $context->getAttribute(),
81
                    'value' => $value,
82
                ],
83
            );
84
        }
85
86 28
        $compoundResult = new Result();
87 28
        $results = [];
88 28
        foreach ($rule->getRules() as $valuePath => $rules) {
89 27
            if ($rule->getRequirePropertyPath() && !ArrayHelper::pathExists($data, $valuePath)) {
90
                /**
91
                 * @psalm-suppress InvalidScalarArgument
92
                 */
93 3
                $compoundResult->addError(
94 3
                    $rule->getNoPropertyPathMessage(),
95
                    [
96 3
                        'path' => $valuePath,
97 3
                        'attribute' => $context->getAttribute(),
98
                    ],
99 3
                    StringHelper::parsePath($valuePath)
100
                );
101
102 3
                continue;
103
            }
104
105 24
            $validatedValue = ArrayHelper::getValueByPath($data, $valuePath);
106 24
            $rules = is_iterable($rules) ? $rules : [$rules];
107
108 24
            $itemResult = $context->getValidator()->validate($validatedValue, $rules);
109
110 24
            if ($itemResult->isValid()) {
111 9
                continue;
112
            }
113
114 19
            $result = new Result();
115
116 19
            foreach ($itemResult->getErrors() as $error) {
117 19
                $errorValuePath = is_int($valuePath) ? [$valuePath] : StringHelper::parsePath($valuePath);
118 19
                if (!empty($error->getValuePath())) {
119 8
                    array_push($errorValuePath, ...$error->getValuePath());
120
                }
121
                /**
122
                 * @psalm-suppress InvalidScalarArgument
123
                 */
124 19
                $result->addError($error->getMessage(), $error->getParameters(), $errorValuePath);
125
            }
126 19
            $results[] = $result;
127
        }
128
129 28
        foreach ($results as $result) {
130 19
            foreach ($result->getErrors() as $error) {
131 19
                $compoundResult->addError($error->getMessage(), $error->getParameters(), $error->getValuePath());
132
            }
133
        }
134
135 28
        return $compoundResult;
136
    }
137
}
138