Passed
Pull Request — master (#320)
by Dmitriy
02:36
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 36
    public function validate(mixed $value, object $rule, ValidationContext $context): Result
48
    {
49 36
        if (!$rule instanceof Nested) {
50 1
            throw new UnexpectedRuleException(Nested::class, $rule);
51
        }
52
53 35
        if ($rule->getRules() === null) {
54 5
            if (!is_object($value)) {
55
                throw new InvalidArgumentException(
56
                    sprintf(
57
                        'Nested rule without rules could be used for objects only. %s given.',
58
                        get_debug_type($value)
59
                    )
60
                );
61
            }
62
63 5
            $dataSet = new ObjectDataSet($value, $rule->getPropertyVisibility());
64
65 5
            return $context->getValidator()->validate($dataSet, $dataSet->getRules(), $context);
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
            $result = new Result();
78 2
            $result->addError(
79
                message:$message,
80 2
                parameters: ['value' => $value]
81
            );
82 2
            return  $result;
83
        }
84
85 28
        $compoundResult = new Result();
86 28
        $results = [];
87 28
        foreach ($rule->getRules() as $valuePath => $rules) {
88 27
            if ($rule->getRequirePropertyPath() && !ArrayHelper::pathExists($data, $valuePath)) {
89
                /**
90
                 * @psalm-suppress InvalidScalarArgument
91
                 */
92 3
                $compoundResult->addError(
93 3
                    message: $rule->getNoPropertyPathMessage(),
94 3
                    valuePath:  StringHelper::parsePath($valuePath),
95 3
                    parameters: ['path' => $valuePath, 'value' => $data]
96
                );
97 3
                continue;
98
            }
99
100 24
            $validatedValue = ArrayHelper::getValueByPath($data, $valuePath);
101 24
            $rules = is_iterable($rules) ? $rules : [$rules];
102
103 24
            $itemResult = $context->getValidator()->validate($validatedValue, $rules, $context);
104
105 24
            if ($itemResult->isValid()) {
106 9
                continue;
107
            }
108
109 19
            $result = new Result();
110
111 19
            foreach ($itemResult->getErrors() as $error) {
112 19
                $errorValuePath = is_int($valuePath) ? [$valuePath] : StringHelper::parsePath($valuePath);
113 19
                if (!empty($error->getValuePath())) {
114 8
                    array_push($errorValuePath, ...$error->getValuePath());
115
                }
116
                /**
117
                 * @psalm-suppress InvalidScalarArgument
118
                 */
119 19
                $result->addError($error->getMessage(), $errorValuePath, $error->getParameters());
120
            }
121 19
            $results[] = $result;
122
        }
123
124 28
        foreach ($results as $result) {
125 19
            foreach ($result->getErrors() as $error) {
126 19
                $compoundResult->addError($error->getMessage(), $error->getValuePath(), $error->getParameters());
127
            }
128
        }
129
130 28
        return $compoundResult;
131
    }
132
}
133