Issues (138)

src/ValidationContext.php (2 issues)

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
    /**
35 11
     * @var ValidatorInterface|null A validator instance. `null` means context data was not set
36
     * with {@see setContextDataOnce()} yet.
37 11
     */
38
    private ?ValidatorInterface $validator = null;
39
40
    /**
41
     * @var mixed The raw validated data. `null` means context data was not set with {@see setContextDataOnce()} yet.
42
     */
43 503
    private mixed $rawData = null;
44
45 503
    /**
46
     * @var DataSetInterface|null Global data set. `null` if data set was not set with {@see setContextDataOnce()} yet.
47
     */
48
    private ?DataSetInterface $globalDataSet = null;
49
50
    /**
51 140
     * @var DataSetInterface|null Current scope's data set the attribute belongs to. `null` if data set was not set
52
     * with {@see setDataSet()} yet.
53 140
     */
54 140
    private ?DataSetInterface $dataSet = null;
55
56
    /**
57
     * @var string|null Validated data set's attribute name. `null` if a single value is validated.
58
     */
59
    private ?string $attribute = null;
60 3
61
    private ?string $attributeLabel = 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->validator !== null) {
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
     * Set attribute translator to use.
118
     *
119
     * @param AttributeTranslatorInterface|null $attributeTranslator Attribute translator to use. If `null`,
120
     * translator passed in {@see setContextData()} will be used.
121
     *
122
     * @return $this The same instance of validation context.
123
     */
124
    public function setAttributeTranslator(?AttributeTranslatorInterface $attributeTranslator): self
125
    {
126
        $this->attributeTranslator = $attributeTranslator;
127
        return $this;
128
    }
129
130
    public function setAttributeLabel(?string $label): self
131
    {
132
        $this->attributeLabel = $label;
133
        return $this;
134
    }
135
136
    /**
137
     * Validate data in current context.
138
     *
139
     * @param mixed $data Data set to validate. If {@see RulesProviderInterface} instance provided and rules are
140
     * not specified explicitly, they are read from the {@see RulesProviderInterface::getRules()}.
141
     * @param callable|iterable|object|string|null $rules Rules to apply. If specified, rules are not read from data set
142
     * even if it is an instance of {@see RulesProviderInterface}.
143
     *
144
     * @psalm-param RawRules|null $rules
145
     *
146
     * @throws RuntimeException If validator is not set in validation context.
147
     *
148
     * @return Result Validation result.
149
     */
150
    public function validate(mixed $data, callable|iterable|object|string|null $rules = null): Result
151
    {
152
        $this->requireValidator();
153
154
        $currentDataSet = $this->dataSet;
155
        $currentAttribute = $this->attribute;
156
        $isCurrentDataSetMissing = $this->isDataSetMissing;
157
158
        // The lack of an attribute means that in the context of further validation there is no data set at all.
159
        $this->isDataSetMissing = $this->isAttributeMissing();
160
        $result = $this->validator->validate($data, $rules, $this);
0 ignored issues
show
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

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