Passed
Pull Request — master (#599)
by Sergei
12:37
created

ValidationContext::setAttribute()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
eloc 2
c 0
b 0
f 0
nc 1
nop 1
dl 0
loc 4
cc 1
rs 10
ccs 0
cts 0
cp 0
crap 2
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Yiisoft\Validator;
6
7
use RuntimeException;
8
use Yiisoft\Arrays\ArrayHelper;
9
use Yiisoft\Validator\Rule\StopOnError;
10
11
/**
12
 * Validation context that might be taken into account when performing validation.
13
 *
14
 * @psalm-import-type RawRules from ValidatorInterface
15
 */
16
final class ValidationContext
17
{
18
    /**
19 827
     * A name of parameter storing validated value as array. For rules working with arrays it helps to prevent extra
20
     * conversion of a validated value to array. The parameter's value type is either `array` or `null`. `null` means
21
     * the original value must be used.
22
     */
23
    public const PARAMETER_VALUE_AS_ARRAY = 'yii-validator-value-as-array';
24
25
    /**
26
     * A name of parameter indicating that previous rule in the set caused validation error. Used to allow skipping of
27 43
     * the current rule:
28
     *
29 43
     * - in {@see Validator} for rules implementing {@see SkipOnErrorInterface}.
30
     * - for {@see StopOnError} rule (no additional configuration is needed).
31
     */
32
    public const PARAMETER_PREVIOUS_RULES_ERRORED = 'yii-validator-previous-rules-errored';
33
34
    public const PARAMETER_PREDEFINED_RESULT = 'yii-validator-predefined-result';
35 11
36
    /**
37 11
     * @var ValidatorInterface|null A validator instance. `null` means context data was not set
38
     * with {@see setContextDataOnce()} yet.
39
     */
40
    private ?ValidatorInterface $validator = null;
41
42
    /**
43 503
     * @var mixed The raw validated data. `null` means context data was not set with {@see setContextDataOnce()} yet.
44
     */
45 503
    private mixed $rawData = null;
46
47
    /**
48
     * @var DataSetInterface|null Global data set. `null` if data set was not set with {@see setContextDataOnce()} yet.
49
     */
50
    private ?DataSetInterface $globalDataSet = null;
51 140
52
    /**
53 140
     * @var DataSetInterface|null Current scope's data set the attribute belongs to. `null` if data set was not set
54 140
     * with {@see setDataSet()} yet.
55
     */
56
    private ?DataSetInterface $dataSet = null;
57
58
    /**
59
     * @var string|null Validated data set's attribute name. `null` if a single value is validated.
60 3
     */
61
    private ?string $attribute = null;
62 3
63
    /**
64
     * @var AttributeTranslatorInterface|null Default attribute translator to use if attribute translator is not set.
65
     */
66
    private ?AttributeTranslatorInterface $defaultAttributeTranslator = null;
67
68
    /**
69
     * @var bool Whether {@see $dataSet} is missing.
70
     */
71
    private bool $isDataSetMissing = false;
72
73
    /**
74
     * @param array $parameters Arbitrary parameters.
75 6
     * @param AttributeTranslatorInterface|null $attributeTranslator Optional attribute translator instance to use.
76
     * If `null` is provided, or it's not specified, a default translator passed through
77 6
     * {@see setContextDataOnce()} is used.
78
     */
79
    public function __construct(
80 503
        private array $parameters = [],
81
        private ?AttributeTranslatorInterface $attributeTranslator = null,
82 503
    ) {
83
    }
84
85 786
    /**
86
     * Set context data if it is not set yet.
87 786
     *
88
     * @param ValidatorInterface $validator A validator instance.
89
     * @param AttributeTranslatorInterface $attributeTranslator Attribute translator to use by default. If translator
90
     * is specified via {@see setAttributeTranslator()}, it will be used instead.
91
     * @param mixed $rawData The raw validated data.
92
     * @param DataSetInterface $dataSet Global data set ({@see $globalDataSet}).
93
     *
94
     * @internal
95
     *
96
     * @return $this The same instance of validation context.
97
     */
98
    public function setContextDataOnce(
99
        ValidatorInterface $validator,
100
        AttributeTranslatorInterface $attributeTranslator,
101
        mixed $rawData,
102
        DataSetInterface $dataSet,
103
    ): self {
104
        if ($this->isInitialized()) {
105
            return $this;
106
        }
107
108
        $this->validator = $validator;
109
        $this->defaultAttributeTranslator = $attributeTranslator;
110
        $this->rawData = $rawData;
111
        $this->globalDataSet = $dataSet;
112
113
        return $this;
114
    }
115
116
    /**
117
     * @internal
118
     */
119
    public function isInitialized(): bool
120
    {
121
        return $this->validator !== null;
122
    }
123
124
    /**
125
     * Set attribute translator to use.
126
     *
127
     * @param AttributeTranslatorInterface|null $attributeTranslator Attribute translator to use. If `null`,
128
     * translator passed in {@see setContextData()} will be used.
129
     *
130
     * @return $this The same instance of validation context.
131
     */
132
    public function setAttributeTranslator(?AttributeTranslatorInterface $attributeTranslator): self
133
    {
134
        $this->attributeTranslator = $attributeTranslator;
135
        return $this;
136
    }
137
138
    /**
139
     * Validate data in current context.
140
     *
141
     * @param mixed $data Data set to validate. If {@see RulesProviderInterface} instance provided and rules are
142
     * not specified explicitly, they are read from the {@see RulesProviderInterface::getRules()}.
143
     * @param callable|iterable|object|string|null $rules Rules to apply. If specified, rules are not read from data set
144
     * even if it is an instance of {@see RulesProviderInterface}.
145
     *
146
     * @psalm-param RawRules|null $rules
147
     *
148
     * @throws RuntimeException If validator is not set in validation context.
149
     *
150
     * @return Result Validation result.
151
     */
152
    public function validate(mixed $data, callable|iterable|object|string|null $rules = null): Result
153
    {
154
        $this->requireValidator();
155
156
        $currentDataSet = $this->dataSet;
157
        $currentAttribute = $this->attribute;
158
        $isCurrentDataSetMissing = $this->isDataSetMissing;
159
160
        // The lack of an attribute means that in the context of further validation there is no data set at all.
161
        $this->isDataSetMissing = $this->isAttributeMissing();
162
        $result = $this->validator->validate($data, $rules, $this);
0 ignored issues
show
Bug introduced by
The method validate() does not exist on null. ( Ignorable by Annotation )

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

162
        /** @scrutinizer ignore-call */ 
163
        $result = $this->validator->validate($data, $rules, $this);

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
163
164
        $this->dataSet = $currentDataSet;
165
        $this->attribute = $currentAttribute;
166
        $this->isDataSetMissing = $isCurrentDataSetMissing;
167
168
        return $result;
169
    }
170
171
    /**
172
     * Get the raw validated data.
173
     *
174
     * @throws RuntimeException If validator is not set in validation context.
175
     *
176
     * @return mixed The raw validated data.
177
     */
178
    public function getRawData(): mixed
179
    {
180
        $this->requireValidator();
181
        return $this->rawData;
182
    }
183
184
    /**
185
     * Get the global data set.
186
     *
187
     * @return DataSetInterface Data set instance.
188
     *
189
     * @see $globalDataSet
190
     */
191
    public function getGlobalDataSet(): DataSetInterface
192
    {
193
        $this->requireValidator();
194
        return $this->globalDataSet;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->globalDataSet could return the type null which is incompatible with the type-hinted return Yiisoft\Validator\DataSetInterface. Consider adding an additional type-check to rule them out.
Loading history...
195
    }
196
197
    /**
198
     * Get the current scope's data set the attribute belongs to.
199
     *
200
     * @return DataSetInterface Data set instance.
201
     *
202
     * @see $dataSet
203
     */
204
    public function getDataSet(): DataSetInterface
205
    {
206
        if ($this->dataSet === null) {
207
            throw new RuntimeException('Data set in validation context is not set.');
208
        }
209
210
        return $this->dataSet;
211
    }
212
213
    /**
214
     * Set the current scope's data set the attribute belongs to.
215
     *
216
     * @param DataSetInterface $dataSet Data set instance.
217
     *
218
     * @return $this The same instance of validation context.
219
     *
220
     * @internal
221
     *
222
     * @see $dataSet
223
     */
224
    public function setDataSet(DataSetInterface $dataSet): self
225
    {
226
        $this->dataSet = $dataSet;
227
        return $this;
228
    }
229
230
    /**
231
     * Get validated data set's attribute name.
232
     *
233
     * @return string|null Validated data set's attribute name. `null` if a single value is validated.
234
     */
235
    public function getAttribute(): ?string
236
    {
237
        return $this->attribute;
238
    }
239
240
    /**
241
     * Get translated attribute name.
242
     *
243
     * @return string|null Translated attribute name. `null` if a single value is validated and there is nothing
244
     * to translate.
245
     */
246
    public function getTranslatedAttribute(): ?string
247
    {
248
        if ($this->attribute === null) {
249
            return null;
250
        }
251
252
        if ($this->attributeTranslator !== null) {
253
            return $this->attributeTranslator->translate($this->attribute);
254
        }
255
256
        if ($this->defaultAttributeTranslator !== null) {
257
            return $this->defaultAttributeTranslator->translate($this->attribute);
258
        }
259
260
        return $this->attribute;
261
    }
262
263
    /**
264
     * Set the name of the attribute validated.
265
     *
266
     * @param string|null $attribute Validated attribute name. Null if a single value is validated.
267
     *
268
     * @return $this The same instance of validation context.
269
     *
270
     * @internal
271
     */
272
    public function setAttribute(?string $attribute): self
273
    {
274
        $this->attribute = $attribute;
275
        return $this;
276
    }
277
278
    /**
279
     * Get named parameter.
280
     *
281
     * @param string $name Parameter name.
282
     * @param mixed $default Default value to return in case parameter with a given name does not exist.
283
     *
284
     * @return mixed Parameter value.
285
     *
286
     * @see ArrayHelper::getValue()
287
     */
288
    public function getParameter(string $name, mixed $default = null): mixed
289
    {
290
        return ArrayHelper::getValue($this->parameters, $name, $default);
291
    }
292
293
    /**
294
     * Set parameter value.
295
     *
296
     * @param string $name Parameter name.
297
     * @param mixed $value Parameter value.
298
     *
299
     * @return $this The same instance of validation context.
300
     */
301
    public function setParameter(string $name, mixed $value): self
302
    {
303
        $this->parameters[$name] = $value;
304
        return $this;
305
    }
306
307
    /**
308
     * Check whether {@see $attribute} is missing in a {@see $dataSet}.
309
     *
310
     * @return bool Whether {@see $attribute} is missing in a {@see $dataSet}.
311
     */
312
    public function isAttributeMissing(): bool
313
    {
314
        return $this->isDataSetMissing
315
            || ($this->attribute !== null && !$this->getDataSet()->hasAttribute($this->attribute));
316
    }
317
318
    /**
319
     * Ensure that validator is set in validation context.
320
     *
321
     * @psalm-assert ValidatorInterface $this->validator
322
     * @psalm-assert DataSetInterface $this->globalDataSet
323
     *
324
     * @throws RuntimeException If validator is not set in validation context.
325
     */
326
    private function requireValidator(): void
327
    {
328
        if ($this->validator === null) {
329
            throw new RuntimeException('Validator is not set in validation context.');
330
        }
331
    }
332
}
333