Passed
Pull Request — master (#300)
by Sergei
02:36
created

Validator::getSkipOnEmptyCallback()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
eloc 1
c 0
b 0
f 0
dl 0
loc 3
ccs 2
cts 2
cp 1
rs 10
cc 1
nc 1
nop 0
crap 1
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Yiisoft\Validator;
6
7
use Closure;
8
use InvalidArgumentException;
9
use JetBrains\PhpStorm\Pure;
10
use Psr\Container\ContainerExceptionInterface;
11
use Psr\Container\NotFoundExceptionInterface;
12
use Yiisoft\Validator\DataSet\ArrayDataSet;
13
use Yiisoft\Validator\DataSet\ObjectDataSet;
14
use Yiisoft\Validator\DataSet\MixedDataSet;
15
use Yiisoft\Validator\Rule\Callback;
16
use Yiisoft\Validator\Rule\Trait\PreValidateTrait;
17
use Yiisoft\Validator\SkipOnEmptyCallback\SkipNone;
18
19
use function gettype;
20
use function is_array;
21
use function is_callable;
22
use function is_int;
23
use function is_object;
24
25
/**
26
 * Validator validates {@link DataSetInterface} against rules set for data set attributes.
27
 */
28
final class Validator implements ValidatorInterface
29
{
30
    use PreValidateTrait;
31
32
    /**
33
     * @var callable|null
34
     */
35
    private $skipOnEmptyCallback;
36
37 632
    public function __construct(
38
        private RuleHandlerResolverInterface $ruleHandlerResolver,
39
40
        /**
41
         * @var bool|callable|null
42
         */
43
        $skipOnEmpty = null,
44
    ) {
45 632
        $this->skipOnEmptyCallback = $skipOnEmpty === null
46 629
            ? null
47 3
            : (SkipOnEmptyNormalizer::normalize($skipOnEmpty) ?? new SkipNone());
48
    }
49
50
    /**
51
     * @param DataSetInterface|mixed|RulesProviderInterface $data
52
     * @param iterable<Closure|Closure[]|RuleInterface|RuleInterface[]>|null $rules
53
     */
54 105
    public function validate(mixed $data, ?iterable $rules = null): Result
55
    {
56 105
        $data = $this->normalizeDataSet($data);
57 105
        if ($rules === null && $data instanceof RulesProviderInterface) {
58 23
            $rules = $data->getRules();
59
        }
60
61 105
        $compoundResult = new Result();
62 105
        $context = new ValidationContext($this, $data);
63 105
        $results = [];
64
65 105
        foreach ($rules ?? [] as $attribute => $attributeRules) {
66 95
            $result = new Result();
67
68 95
            $tempRule = is_array($attributeRules) ? $attributeRules : [$attributeRules];
69 95
            $attributeRules = $this->normalizeRules($tempRule);
70
71 95
            if (is_int($attribute)) {
72 37
                $validatedData = $data->getData();
73 37
                $validatedContext = $context;
74
            } else {
75 62
                $validatedData = $data->getAttributeValue($attribute);
76 62
                $validatedContext = $context->withAttribute($attribute);
77
            }
78
79 95
            $tempResult = $this->validateInternal(
80
                $validatedData,
81
                $attributeRules,
82
                $validatedContext
83
            );
84
85 90
            $result = $this->addErrors($result, $tempResult->getErrors());
86 90
            $results[] = $result;
87
        }
88
89 100
        foreach ($results as $result) {
90 90
            $compoundResult = $this->addErrors($compoundResult, $result->getErrors());
91
        }
92
93 100
        if ($data instanceof PostValidationHookInterface) {
94
            $data->processValidationResult($compoundResult);
95
        }
96
97 100
        return $compoundResult;
98
    }
99
100 105
    #[Pure]
101
    private function normalizeDataSet($data): DataSetInterface
102
    {
103 105
        if ($data instanceof DataSetInterface) {
104 47
            return $data;
105
        }
106
107 63
        if (is_object($data)) {
108 25
            return new ObjectDataSet($data);
109
        }
110
111 42
        if (is_array($data)) {
112 13
            return new ArrayDataSet($data);
113
        }
114
115 39
        return new MixedDataSet($data);
116
    }
117
118
    /**
119
     * @param $value
120
     * @param iterable<Closure|Closure[]|RuleInterface|RuleInterface[]> $rules
121
     * @param ValidationContext $context
122
     *
123
     * @throws ContainerExceptionInterface
124
     * @throws NotFoundExceptionInterface
125
     *
126
     * @return Result
127
     */
128 95
    private function validateInternal($value, iterable $rules, ValidationContext $context): Result
129
    {
130 95
        $compoundResult = new Result();
131 95
        foreach ($rules as $rule) {
132 95
            if ($rule instanceof BeforeValidationInterface) {
133 93
                $preValidateResult = $this->preValidate($value, $context, $rule);
134 93
                if ($preValidateResult) {
135 14
                    continue;
136
                }
137
            }
138
139 91
            $ruleHandler = $this->ruleHandlerResolver->resolve($rule->getHandlerClassName());
140 89
            $ruleResult = $ruleHandler->validate($value, $rule, $context);
141 86
            if ($ruleResult->isValid()) {
142 36
                continue;
143
            }
144
145 70
            $context->setParameter($this->parameterPreviousRulesErrored, true);
146
147 70
            foreach ($ruleResult->getErrors() as $error) {
148 70
                $valuePath = $error->getValuePath();
149 70
                if ($context->getAttribute() !== null) {
150 49
                    $valuePath = [$context->getAttribute(), ...$valuePath];
151
                }
152 70
                $compoundResult->addError($error->getMessage(), $valuePath);
153
            }
154
        }
155 90
        return $compoundResult;
156
    }
157
158
    /**
159
     * @param array $rules
160
     *
161
     * @return iterable<RuleInterface>
162
     */
163 95
    private function normalizeRules(iterable $rules): iterable
164
    {
165 95
        foreach ($rules as $rule) {
166 95
            yield $this->normalizeRule($rule);
167
        }
168
    }
169
170 95
    private function normalizeRule($rule): RuleInterface
171
    {
172 95
        if (is_callable($rule)) {
173 3
            return new Callback($rule);
174
        }
175
176 95
        if (!$rule instanceof RuleInterface) {
177
            throw new InvalidArgumentException(
178
                sprintf(
179
                    'Rule should be either an instance of %s or a callable, %s given.',
180
                    RuleInterface::class,
181
                    gettype($rule)
182
                )
183
            );
184
        }
185
186 95
        if ($this->skipOnEmptyCallback !== null) {
187 14
            $rule = $rule->skipOnEmpty($this->skipOnEmptyCallback ?? false);
0 ignored issues
show
Bug introduced by
The method skipOnEmpty() does not exist on Yiisoft\Validator\RuleInterface. It seems like you code against a sub-type of said class. However, the method does not exist in Yiisoft\Validator\SerializableRuleInterface or anonymous//tests/ValidatorTest.php$1 or anonymous//tests/ValidatorTest.php$2 or Yiisoft\Validator\Tests\Stub\Rule. Are you sure you never get one of those? ( Ignorable by Annotation )

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

187
            /** @scrutinizer ignore-call */ 
188
            $rule = $rule->skipOnEmpty($this->skipOnEmptyCallback ?? false);
Loading history...
188
        }
189
190 95
        return $rule;
191
    }
192
193 90
    private function addErrors(Result $result, array $errors): Result
194
    {
195 90
        foreach ($errors as $error) {
196 70
            $result->addError($error->getMessage(), $error->getValuePath());
197
        }
198 90
        return $result;
199
    }
200
}
201