Passed
Push — master ( c9bbfa...c0c608 )
by Alexander
13:38 queued 11:17
created

Validator::__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 0
Metric Value
eloc 0
c 0
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;
6
7
use InvalidArgumentException;
8
use JetBrains\PhpStorm\Pure;
9
use Psr\Container\ContainerExceptionInterface;
10
use Psr\Container\NotFoundExceptionInterface;
11
use Yiisoft\Validator\DataSet\ArrayDataSet;
12
use Yiisoft\Validator\DataSet\ScalarDataSet;
13
use Yiisoft\Validator\Rule\Callback;
14
15
use Yiisoft\Validator\Rule\Trait\PreValidateTrait;
16
use function is_array;
17
use function is_object;
18
19
/**
20
 * Validator validates {@link DataSetInterface} against rules set for data set attributes.
21
 */
22
final class Validator implements ValidatorInterface
23
{
24
    use PreValidateTrait;
25
26 572
    public function __construct(private RuleHandlerResolverInterface $ruleHandlerResolver)
27
    {
28
    }
29
30
    /**
31
     * @param DataSetInterface|mixed|RulesProviderInterface $data
32
     * @param iterable<\Closure|\Closure[]|RuleInterface|RuleInterface[]>|null $rules
33
     */
34 36
    public function validate(mixed $data, ?iterable $rules = null): Result
35
    {
36 36
        $data = $this->normalizeDataSet($data);
37 36
        if ($rules === null && $data instanceof RulesProviderInterface) {
38 2
            $rules = $data->getRules();
39
        }
40
41 36
        $compoundResult = new Result();
42 36
        $context = new ValidationContext($this, $data);
43 36
        $results = [];
44
45 36
        foreach ($rules ?? [] as $attribute => $attributeRules) {
46 35
            $result = new Result();
47
48 35
            $tempRule = is_array($attributeRules) ? $attributeRules : [$attributeRules];
49 35
            $attributeRules = $this->normalizeRules($tempRule);
50
51 35
            if (is_int($attribute)) {
52 19
                $validatedData = $data->getData();
53 19
                $validatedContext = $context;
54
            } else {
55 16
                $validatedData = $data->getAttributeValue($attribute);
56 16
                $validatedContext = $context->withAttribute($attribute);
57
            }
58
59 35
            $tempResult = $this->validateInternal(
60
                $validatedData,
61
                $attributeRules,
62
                $validatedContext
63
            );
64
65 33
            $result = $this->addErrors($result, $tempResult->getErrors());
66 33
            $results[] = $result;
67
        }
68
69 34
        foreach ($results as $result) {
70 33
            $compoundResult = $this->addErrors($compoundResult, $result->getErrors());
71
        }
72
73 34
        if ($data instanceof PostValidationHookInterface) {
74
            $data->processValidationResult($compoundResult);
75
        }
76
77 34
        return $compoundResult;
78
    }
79
80 36
    #[Pure]
81
    private function normalizeDataSet($data): DataSetInterface
82
    {
83 36
        if ($data instanceof DataSetInterface) {
84 10
            return $data;
85
        }
86
87 26
        if (is_object($data) || is_array($data)) {
88 6
            return new ArrayDataSet((array)$data);
89
        }
90
91 25
        return new ScalarDataSet($data);
92
    }
93
94
    /**
95
     * @param $value
96
     * @param iterable<\Closure|\Closure[]|RuleInterface|RuleInterface[]> $rules
97
     * @param ValidationContext $context
98
     *
99
     * @throws ContainerExceptionInterface
100
     * @throws NotFoundExceptionInterface
101
     *
102
     * @return Result
103
     */
104 35
    private function validateInternal($value, iterable $rules, ValidationContext $context): Result
105
    {
106 35
        $compoundResult = new Result();
107 35
        foreach ($rules as $rule) {
108 35
            if ($rule instanceof BeforeValidationInterface) {
109 33
                $preValidateResult = $this->preValidate($value, $context, $rule);
110 33
                if ($preValidateResult) {
111 2
                    continue;
112
                }
113
            }
114
115 33
            $ruleHandler = $this->ruleHandlerResolver->resolve($rule->getHandlerClassName());
116 31
            $ruleResult = $ruleHandler->validate($value, $rule, $context);
117 31
            if ($ruleResult->isValid()) {
118 21
                continue;
119
            }
120
121 18
            $context->setParameter($this->parameterPreviousRulesErrored, true);
122
123 18
            foreach ($ruleResult->getErrors() as $error) {
124 18
                $valuePath = $error->getValuePath();
125 18
                if ($context->getAttribute() !== null) {
126 3
                    $valuePath = [$context->getAttribute()] + $valuePath;
127
                }
128 18
                $compoundResult->addError($error->getMessage(), $valuePath);
129
            }
130
        }
131 33
        return $compoundResult;
132
    }
133
134
    /**
135
     * @param array $rules
136
     *
137
     * @return iterable<RuleInterface>
138
     */
139 35
    private function normalizeRules(iterable $rules): iterable
140
    {
141 35
        foreach ($rules as $rule) {
142 35
            yield $this->normalizeRule($rule);
143
        }
144
    }
145
146 35
    private function normalizeRule($rule): RuleInterface
147
    {
148 35
        if (is_callable($rule)) {
149 3
            return new Callback($rule);
150
        }
151
152 35
        if (!$rule instanceof RuleInterface) {
153
            throw new InvalidArgumentException(
154
                sprintf(
155
                    'Rule should be either an instance of %s or a callable, %s given.',
156
                    RuleInterface::class,
157
                    gettype($rule)
158
                )
159
            );
160
        }
161
162 35
        return $rule;
163
    }
164
165 33
    private function addErrors(Result $result, array $errors): Result
166
    {
167 33
        foreach ($errors as $error) {
168 18
            $result->addError($error->getMessage(), $error->getValuePath());
169
        }
170 33
        return $result;
171
    }
172
}
173