Passed
Pull Request — master (#394)
by Sergei
29:24 queued 26:09
created

Validator::createDefaultTranslator()   A

Complexity

Conditions 2
Paths 1

Size

Total Lines 11
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 8
CRAP Score 2

Importance

Changes 0
Metric Value
eloc 7
c 0
b 0
f 0
dl 0
loc 11
ccs 8
cts 8
cp 1
rs 10
cc 2
nc 1
nop 0
crap 2
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Yiisoft\Validator;
6
7
use ReflectionException;
8
use ReflectionProperty;
9
use Yiisoft\Translator\CategorySource;
10
use Yiisoft\Translator\IdMessageReader;
11
use Yiisoft\Translator\IntlMessageFormatter;
12
use Yiisoft\Translator\SimpleMessageFormatter;
13
use Yiisoft\Translator\Translator;
14
use Yiisoft\Translator\TranslatorInterface;
15
use Yiisoft\Validator\Helper\RulesNormalizer;
16
use Yiisoft\Validator\Helper\SkipOnEmptyNormalizer;
17
use Yiisoft\Validator\Rule\Trait\PreValidateTrait;
18
19
use function extension_loaded;
20
use function is_int;
21
22
/**
23
 * Validator validates {@link DataSetInterface} against rules set for data set attributes.
24
 *
25
 * @psalm-import-type RulesType from ValidatorInterface
26
 */
27
final class Validator implements ValidatorInterface
28
{
29
    use PreValidateTrait;
30
31
    private RuleHandlerResolverInterface $ruleHandlerResolver;
32
    private TranslatorInterface $translator;
33
34
    /**
35
     * @var callable
36
     */
37
    private $defaultSkipOnEmptyCriteria;
38
39
    /**
40
     * @param int $rulesPropertyVisibility What visibility levels to use when reading rules from the class specified in
41
     * `$rules` argument in {@see validate()} method.
42
     */
43 806
    public function __construct(
44
        ?RuleHandlerResolverInterface $ruleHandlerResolver = null,
45
        ?TranslatorInterface $translator = null,
46
        private int $rulesPropertyVisibility = ReflectionProperty::IS_PRIVATE
47
        | ReflectionProperty::IS_PROTECTED
48
        | ReflectionProperty::IS_PUBLIC,
49
        bool|callable|null $defaultSkipOnEmpty = null,
50
        private string $translationCategory = 'yii-validator',
51
    ) {
52 806
        $this->ruleHandlerResolver = $ruleHandlerResolver ?? new SimpleRuleHandlerContainer();
53 806
        $this->translator = $translator ?? $this->createDefaultTranslator();
54 806
        $this->defaultSkipOnEmptyCriteria = SkipOnEmptyNormalizer::normalize($defaultSkipOnEmpty);
55
    }
56
57
    /**
58
     * @param DataSetInterface|mixed|RulesProviderInterface $data
59
     *
60
     * @psalm-param RulesType $rules
61
     *
62
     * @throws ReflectionException
63
     */
64 826
    public function validate(
65
        mixed $data,
66
        iterable|object|string|null $rules = null,
67
        ?ValidationContext $context = null
68
    ): Result {
69 826
        $data = DataSetHelper::normalize($data);
70 826
        $rules = RulesNormalizer::normalize(
71
            $rules,
72 826
            $this->rulesPropertyVisibility,
73
            $data,
74 826
            $this->defaultSkipOnEmptyCriteria
75
        );
76
77 825
        $compoundResult = new Result();
78 825
        $context ??= new ValidationContext($this, $data);
79 825
        $results = [];
80
81 825
        foreach ($rules as $attribute => $attributeRules) {
82 814
            $result = new Result();
83
84 814
            if (is_int($attribute)) {
85
                /** @psalm-suppress MixedAssignment */
86 674
                $validatedData = $data->getData();
87
            } else {
88
                /** @psalm-suppress MixedAssignment */
89 144
                $validatedData = $data->getAttributeValue($attribute);
90 144
                $context->setAttribute($attribute);
91
            }
92
93 814
            $tempResult = $this->validateInternal($validatedData, $attributeRules, $context);
94
95 790
            foreach ($tempResult->getErrors() as $error) {
96 503
                $result->addError($error->getMessage(), $error->getParameters(), $error->getValuePath());
97
            }
98
99 790
            $results[] = $result;
100
        }
101
102 800
        foreach ($results as $result) {
103 789
            foreach ($result->getErrors() as $error) {
104 502
                $compoundResult->addError(
105 502
                    $this->translator->translate(
106 502
                        $error->getMessage(),
107 502
                        $error->getParameters(),
108 502
                        $this->translationCategory
109
                    ),
110 502
                    $error->getParameters(),
111 502
                    $error->getValuePath()
112
                );
113
            }
114
        }
115
116 800
        if ($data instanceof PostValidationHookInterface) {
117 1
            $data->processValidationResult($compoundResult);
118
        }
119
120 800
        return $compoundResult;
121
    }
122
123
    /**
124
     * @param iterable<RuleInterface> $rules
125
     */
126 814
    private function validateInternal(mixed $value, iterable $rules, ValidationContext $context): Result
127
    {
128 814
        $compoundResult = new Result();
129 814
        foreach ($rules as $rule) {
130 814
            if ($this->preValidate($value, $context, $rule)) {
131 29
                continue;
132
            }
133
134 808
            $ruleHandler = $this->ruleHandlerResolver->resolve($rule->getHandlerClassName());
135 806
            $ruleResult = $ruleHandler->validate($value, $rule, $context);
136 784
            if ($ruleResult->isValid()) {
137 306
                continue;
138
            }
139
140 503
            $context->setParameter($this->parameterPreviousRulesErrored, true);
141
142 503
            foreach ($ruleResult->getErrors() as $error) {
143 503
                $valuePath = $error->getValuePath();
144 503
                if ($context->getAttribute() !== null) {
145 118
                    $valuePath = [$context->getAttribute(), ...$valuePath];
146
                }
147 503
                $compoundResult->addError($error->getMessage(), $error->getParameters(), $valuePath);
148
            }
149
        }
150 790
        return $compoundResult;
151
    }
152
153 3
    private function createDefaultTranslator(): Translator
154
    {
155 3
        $categorySource = new CategorySource(
156 3
            $this->translationCategory,
157 3
            new IdMessageReader(),
158 3
            extension_loaded('intl') ? new IntlMessageFormatter() : new SimpleMessageFormatter(),
159
        );
160 3
        $translator = new Translator();
161 3
        $translator->addCategorySources($categorySource);
162
163 3
        return $translator;
164
    }
165
}
166