Passed
Pull Request — master (#564)
by Sergei
20:34 queued 17:40
created

Nested   B

Complexity

Total Complexity 50

Size/Duplication

Total Lines 497
Duplicated Lines 0 %

Test Coverage

Coverage 100%

Importance

Changes 5
Bugs 3 Features 0
Metric Value
eloc 130
dl 0
loc 497
rs 8.4
c 5
b 3
f 0
ccs 81
cts 81
cp 1
wmc 50

17 Methods

Rating   Name   Duplication   Size   Complexity  
A getValidatedObjectPropertyVisibility() 0 3 1
A getNoRulesWithNoObjectMessage() 0 3 1
A getOptions() 0 33 2
A getRules() 0 3 1
B afterInitAttribute() 0 16 7
A getHandler() 0 3 1
A ensureArrayHasRules() 0 18 5
A __construct() 0 20 1
A propagateOptions() 0 14 4
A getIncorrectInputMessage() 0 3 1
B prepareRules() 0 33 8
B handleEachShortcut() 0 57 8
A flattenKeys() 0 19 6
A getNoPropertyPathMessage() 0 3 1
A getName() 0 3 1
A isPropertyPathRequired() 0 3 1
A getIncorrectDataSetTypeMessage() 0 3 1

How to fix   Complexity   

Complex Class

Complex classes like Nested often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Nested, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
declare(strict_types=1);
4
5
namespace Yiisoft\Validator\Rule;
6
7
use Attribute;
8
use Closure;
9
use InvalidArgumentException;
10
use JetBrains\PhpStorm\ArrayShape;
11
use ReflectionProperty;
12
use Traversable;
13
use Yiisoft\Strings\StringHelper;
14
use Yiisoft\Validator\AfterInitAttributeEventInterface;
15
use Yiisoft\Validator\DataSet\ObjectDataSet;
16
use Yiisoft\Validator\Helper\PropagateOptionsHelper;
17
use Yiisoft\Validator\PropagateOptionsInterface;
18
use Yiisoft\Validator\Rule\Trait\SkipOnEmptyTrait;
19
use Yiisoft\Validator\Rule\Trait\SkipOnErrorTrait;
20
use Yiisoft\Validator\Rule\Trait\WhenTrait;
21
use Yiisoft\Validator\RuleInterface;
22
use Yiisoft\Validator\Helper\RulesDumper;
23
use Yiisoft\Validator\RulesProvider\AttributesRulesProvider;
24
use Yiisoft\Validator\RulesProviderInterface;
25
use Yiisoft\Validator\RuleWithOptionsInterface;
26
use Yiisoft\Validator\SkipOnEmptyInterface;
27
use Yiisoft\Validator\SkipOnErrorInterface;
28
use Yiisoft\Validator\Tests\Rule\NestedTest;
29
use Yiisoft\Validator\WhenInterface;
30
31
use function array_pop;
32
use function count;
0 ignored issues
show
Bug introduced by
This use statement conflicts with another class in this namespace, Yiisoft\Validator\Rule\count. Consider defining an alias.

Let?s assume that you have a directory layout like this:

.
|-- OtherDir
|   |-- Bar.php
|   `-- Foo.php
`-- SomeDir
    `-- Foo.php

and let?s assume the following content of Bar.php:

// Bar.php
namespace OtherDir;

use SomeDir\Foo; // This now conflicts the class OtherDir\Foo

If both files OtherDir/Foo.php and SomeDir/Foo.php are loaded in the same runtime, you will see a PHP error such as the following:

PHP Fatal error:  Cannot use SomeDir\Foo as Foo because the name is already in use in OtherDir/Foo.php

However, as OtherDir/Foo.php does not necessarily have to be loaded and the error is only triggered if it is loaded before OtherDir/Bar.php, this problem might go unnoticed for a while. In order to prevent this error from surfacing, you must import the namespace with a different alias:

// Bar.php
namespace OtherDir;

use SomeDir\Foo as SomeDirFoo; // There is no conflict anymore.
Loading history...
33
use function implode;
34
use function is_array;
35
use function is_int;
36
use function is_string;
37
use function ltrim;
38
use function rtrim;
39
use function sprintf;
40
41
/**
42
 * Used to define rules for validation of nested structures:
43
 *
44
 * - For one-to-one relation, using `Nested` rule is enough.
45
 * - One-to-many and many-to-many relations require pairing with {@see Each} rule.
46
 *
47
 * An example with blog post:
48
 *
49
 * ```php
50
 * $rule = new Nested([
51
 *     'title' => [new Length(max: 255)],
52
 *     // One-to-one relation
53
 *     'author' => new Nested([
54
 *         'name' => [new Length(min: 1)],
55
 *     ]),
56
 *     // One-to-many relation
57
 *     'files' => new Each([
58
 *          new Nested([
59
 *             'url' => [new Url()],
60
 *         ]),
61
 *     ]),
62
 * ]);
63
 * ```
64
 *
65
 * There is an alternative way to write this using dot notation and shortcuts:
66
 *
67
 * ```php
68
 * $rule = new Nested([
69
 *     'title' => [new Length(max: 255)],
70
 *     'author.name' => [new Length(min: 1)],
71
 *     'files.*.url' => [new Url()],
72
 * ]);
73
 * ```
74
 *
75 26
 * Also it's possible to use plain keys and omit arrays for single rules:
76
 *
77
 *  * ```php
78
 * $rules = [
79
 *     new Nested([
80
 *         'author' => [
81
 *             'name' => new Length(min: 1),
82
 *         ],
83
 *     ]),
84
 * ];
85
 * ```
86
 *
87
 * For more examples please refer to the guide.
88
 *
89
 * It's also possible to use DTO objects with PHP attributes, see {@see ObjectDataSet} documentation and guide for
90
 * details.
91
 *
92
 * Supports propagation of options (see {@see PropagateOptionsHelper::propagate()} for available options and
93
 * requirements).
94
 *
95
 * @see NestedHandler Corresponding handler performing the actual validation.
96
 *
97
 * @psalm-import-type WhenType from WhenInterface
98
 * @psalm-type RawRulesType = array<array<RuleInterface>|RuleInterface>
99
 * @psalm-type ReadyRulesType = array<list<RuleInterface>|RuleInterface>
100
 * @psalm-type OptionalReadyRulesType = ReadyRulesType|null
101
 */
102
#[Attribute(Attribute::TARGET_CLASS | Attribute::TARGET_PROPERTY | Attribute::IS_REPEATABLE)]
103
final class Nested implements
104
    RuleWithOptionsInterface,
105
    SkipOnErrorInterface,
106
    WhenInterface,
107
    SkipOnEmptyInterface,
108
    PropagateOptionsInterface,
109
    AfterInitAttributeEventInterface
110
{
111 26
    use SkipOnEmptyTrait;
112
    use SkipOnErrorTrait;
113
    use WhenTrait;
114 2
115
    /**
116 2
     * A character acting as a separator when using alternative (short) syntax.
117
     */
118
    private const SEPARATOR = '.';
119
    /**
120
     * A character acting as a shortcut when using alternative (short) syntax with {@see Nested} and {@see Each}
121
     * combinations.
122 53
     */
123
    private const EACH_SHORTCUT = '*';
124 53
125
    /**
126
     * @var OptionalReadyRulesType A set of ready to use rule instances. The 1st level is always
0 ignored issues
show
Bug introduced by
The type Yiisoft\Validator\Rule\OptionalReadyRulesType was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
127 11
     * an array of rules, the 2nd level is either a list of rules or a single rule.
128
     */
129 11
    private array|null $rules;
130
131
    /**
132 5
     * @param iterable|object|string|null $rules Rules for validating nested structure. The following types are
133
     * supported:
134 5
     *
135
     * - Array or object implementing {@see Traversable} interface containing rules. Either iterables containing
136
     * {@see RuleInterface} implementations or a single rule instances are expected. All iterables regardless of the
137 3
     * nesting level will be converted to arrays at the end.
138
     * - Object implementing {@see RulesProviderInterface}.
139 3
     * - Name of a class containing rules declared via PHP attributes.
140
     * - `null` if validated value is an object. It can either implement {@see RulesProviderInterface} or contain rules
141
     * declared via PHP attributes.
142 5
     * @psalm-param iterable|object|class-string|null $rules
143
     *
144 5
     * @param int $validatedObjectPropertyVisibility Visibility levels to use for parsed properties when validated value
145
     * is an object providing rules / data. For example: public and protected only, this means that the rest (private
146
     * ones) will be skipped. Defaults to all visibility levels (public, protected and private). See
147 39
     * {@see ObjectDataSet} for details on providing rules / data in validated object and {@see ObjectParser} for
148
     * overview how parsing works.
149 39
     * @psalm-param int-mask-of<ReflectionProperty::IS_*> $validatedObjectPropertyVisibility
150
     *
151
     * @param int $rulesSourceClassPropertyVisibility Visibility levels to use for parsed properties when {@see $rules}
152 10
     * source is a name of the class providing rules. For example: public and protected only, this means that the rest
153
     * (private ones) will be skipped. Defaults to all visibility levels (public, protected and private). See
154 10
     * {@see ObjectDataSet} for details on providing rules via class and {@see ObjectParser} for overview how parsing
155
     * works.
156
     * @psalm-param int-mask-of<ReflectionProperty::IS_*> $rulesSourceClassPropertyVisibility
157
     *
158
     * @param string $noRulesWithNoObjectMessage Error message used when validation fails because the validated value is
159
     * not an object and the rules were not explicitly specified via {@see $rules}:
160 26
     *
161
     * You may use the following placeholders in the message:
162 26
     *
163 16
     * - `{attribute}`: the translated label of the attribute being validated.
164
     * - `{type}`: the type of the value being validated.
165 16
     * @param string $incorrectDataSetTypeMessage Error message used when validation fails because the validated value
166
     * is an object providing wrong type of data (neither array nor an object).
167
     *
168 10
     * You may use the following placeholders in the message:
169 1
     *
170 9
     * - `{type}`: the type of the data set retrieved from the validated object.
171 4
     * @param string $incorrectInputMessage Error message used when validation fails because the validated value is
172
     * neither an array nor an object.
173 5
     *
174
     * You may use the following placeholders in the message:
175
     *
176 10
     * - `{attribute}`: the translated label of the attribute being validated.
177 8
     * - `{type}`: the type of the value being validated.
178
     * @param bool $requirePropertyPath Whether to require a single data item to be passed in data according to declared
179 8
     * nesting level structure (all keys in the sequence must be the present). Used only when validated value is an
180 8
     * array. Enabled by default. See {@see $noPropertyPathMessage} for customization of error message.
181
     * @param string $noPropertyPathMessage Error message used when validation fails because {@see $requirePropertyPath}
182
     * option was enabled and the validated array contains missing data item.
183 7
     *
184 1
     * You may use the following placeholders in the message:
185
     *
186
     * - `{path}`: the path of the value being validated. Can be either a simple key of integer / string type for a
187
     * single nesting level or a sequence of keys concatenated using dot notation (see {@see SEPARATOR}).
188
     * - `{attribute}`: the translated label of the attribute being validated.
189
     * @param bool $handleEachShortcut Whether to handle {@see EACH_SHORTCUT}. Enabled by default meaning shortcuts are
190
     * supported. Can be disabled if they are not used to prevent additional checks and improve performance.
191 10
     * @param bool $propagateOptions Whether the propagation of options is enabled (see
192
     * {@see PropagateOptionsHelper::propagate()} for supported options and requirements). Disabled by default.
193 10
     * @param bool|callable|null $skipOnEmpty Whether to skip this `Nested` rule with all defined {@see $rules} if the
194
     * validated value is empty / not passed. See {@see SkipOnEmptyInterface}.
195 10
     * @param bool $skipOnError Whether to skip this `Nested` rule with all defined {@see $rules} if any of the previous
196 9
     * rules gave an error. See {@see SkipOnErrorInterface}.
197 7
     * @param Closure|null $when  A callable to define a condition for applying this `Nested` rule with all defined
198 9
     * {@see $rules}. See {@see WhenInterface}.
199 2
     * @psalm-param WhenType $when
200
     */
201
    public function __construct(
202 2
        iterable|object|string|null $rules = null,
203
        private int $validatedObjectPropertyVisibility = ReflectionProperty::IS_PRIVATE
204
        | ReflectionProperty::IS_PROTECTED
205 2
        | ReflectionProperty::IS_PUBLIC,
206
        private int $rulesSourceClassPropertyVisibility = ReflectionProperty::IS_PRIVATE
207
        | ReflectionProperty::IS_PROTECTED
208
        | ReflectionProperty::IS_PUBLIC,
209
        private string $noRulesWithNoObjectMessage = 'Nested rule without rules can be used for objects only.',
210 8
        private string $incorrectDataSetTypeMessage = 'An object data set data can only have an array type.',
211
        private string $incorrectInputMessage = 'The value must be an array or an object.',
212
        private bool $requirePropertyPath = false,
213 8
        private string $noPropertyPathMessage = 'Property "{path}" is not found.',
214 8
        private bool $handleEachShortcut = true,
215 8
        private bool $propagateOptions = false,
216 8
        private mixed $skipOnEmpty = null,
217
        private bool $skipOnError = false,
218 8
        private Closure|null $when = null,
219 7
    ) {
220 1
        $this->prepareRules($rules);
221
    }
222
223 6
    public function getName(): string
224
    {
225
        return 'nested';
226
    }
227
228 6
    /**
229 6
     * Gets a set of rules for running the validation.
230
     *
231
     * @return array|null A set of rules. `null` means the rules are expected to be provided with a validated value.
232
     * @psalm-return ReadyRulesType
233
     */
234
    public function getRules(): array|null
235
    {
236
        return $this->rules;
237
    }
238
239
    /**
240
     * Gets visibility levels to use for parsed properties when validated value is an object providing rules / data.
241
     * Defaults to all visibility levels (public, protected and private)
242
     *
243
     * @return int A number representing visibility levels.
244
     * @psalm-return int-mask-of<ReflectionProperty::IS_*>
245
     *
246
     * @see $validatedObjectPropertyVisibility
247
     */
248
    public function getValidatedObjectPropertyVisibility(): int
249
    {
250
        return $this->validatedObjectPropertyVisibility;
251
    }
252
253
    /**
254
     * Gets error message used when validation fails because the validated value is not an object and the rules were not
255
     * explicitly specified via {@see $rules}.
256 7
     *
257
     * @return string Error message / template.
258
     *
259
     * @see $incorrectInputMessage
260
     */
261
    public function getNoRulesWithNoObjectMessage(): string
262
    {
263
        return $this->noRulesWithNoObjectMessage;
264
    }
265
266
    /**
267 7
     * Gets error message used when validation fails because the validated value is an object providing wrong type of
268 7
     * data (neither array nor an object).
269
     *
270
     * @return string Error message / template.
271
     *
272 7
     * @see $incorrectDataSetTypeMessage
273
     */
274
    public function getIncorrectDataSetTypeMessage(): string
275 1
    {
276
        return $this->incorrectDataSetTypeMessage;
277 1
    }
278
279
    /**
280
     * Gets error message used when validation fails because the validated value is neither an array nor an object.
281
     *
282
     * @return string Error message / template.
283
     *
284
     * @see $incorrectInputMessage
285
     */
286 1
    public function getIncorrectInputMessage(): string
287 1
    {
288 1
        return $this->incorrectInputMessage;
289 1
    }
290
291 1
    /**
292 1
     * Whether to require a single data item to be passed in data according to declared nesting level structure (all
293
     * keys in the sequence must be the present). Enabled by default.
294 1
     *
295 1
     * @return bool `true` if required and `false` otherwise.
296
     *
297
     * @see $requirePropertyPath
298 1
     */
299
    public function isPropertyPathRequired(): bool
300 1
    {
301 1
        return $this->requirePropertyPath;
302
    }
303
304
    /**
305
     * Gets error message used when validation fails because {@see $requirePropertyPath} option was enabled and the
306 1
     * validated array contains missing data item.
307
     *
308
     * @return string Error message / template.
309 5
     *
310
     * @see $getNoPropertyPathMessage
311
     */
312
    public function getNoPropertyPathMessage(): string
313
    {
314
        return $this->noPropertyPathMessage;
315
    }
316
317
    /**
318
     * Prepares raw rules passed in the constructor for usage in handler. As a result, {@see $rules} property will
319
     * contain ready to use rules.
320
     *
321
     * @param iterable|object|string|null $source Raw rules passed in the constructor.
322
     *
323 5
     * @throws InvalidArgumentException When rules' source has wrong type.
324
     * @throws InvalidArgumentException When source contains items that are not rules.
325
     */
326
    private function prepareRules(iterable|object|string|null $source): void
327 5
    {
328
        if ($source === null) {
329
            $this->rules = null;
330
            return;
331 5
        }
332
333
        if ($source instanceof RulesProviderInterface) {
334
            $rules = $source->getRules();
335 5
        } elseif (is_string($source) && class_exists($source)) {
336
            $rules = (new AttributesRulesProvider($source, $this->rulesSourceClassPropertyVisibility))->getRules();
337
        } elseif (is_iterable($source)) {
338 5
            $rules = $source;
339 5
        } else {
340 5
            throw new InvalidArgumentException(
341 5
                'The $rules argument passed to Nested rule can be either: a null, an object implementing ' .
342
                'RulesProviderInterface, a class string or an iterable.'
343
            );
344
        }
345 53
346
        self::ensureArrayHasRules($rules);
347 53
348
        if ($this->handleEachShortcut) {
349
            $this->handleEachShortcut($rules);
350
        }
351
352
        $preparedRules = [];
353
        $this->flattenKeys($rules, $preparedRules);
354
355
        $this->rules = $preparedRules;
356
357
        if ($this->propagateOptions) {
358
            $this->propagateOptions();
359
        }
360
    }
361
362
    /**
363
     * Recursively flattens plain keys to allow any nesting level. The keys in the result array are joined using
364
     * {@see SEPARATOR}.
365
     *
366
     * Example of input:
367
     *
368
     * ```php
369
     * [
370
     *     'key1' => [
371
     *         'key2' => [
372
     *             'key3 => [
373
     *                 // ...
374
     *             ],
375
     *         ],
376
     *     ],
377
     * ];
378
     * ```
379
     *
380
     * Example of output for default {@see SEPARATOR}:
381
     *
382
     * ```php
383
     * [
384
     *     'key1.key2.key3' => [
385
     *         // ...
386
     *     ],
387
     * ],
388
     * ```
389
     *
390
     * @param array $rawRules Raw rules array which keys need to be flattened.
391
     * @psalm-param RawRulesType $rawRules
392
     *
393
     * @param array $resultRules Result rules array with flattened keys passed by reference.
394
     * @psalm-param ReadyRulesType $resultRules
395
     *
396
     * @param string|null $baseValuePath Base value path string. Can be a single key or multiple keys joined with
397
     * {@see SEPARATOR}. `null` is used for the first call.
398
     */
399
    private function flattenKeys(array $rawRules, array &$resultRules, ?string $baseValuePath = null): void
400
    {
401
        foreach ($rawRules as $valuePath => $validationRules) {
402
            if (is_int($valuePath)) {
403
                $key = $baseValuePath;
404
            } else {
405
                $key = ($baseValuePath !== null ? $baseValuePath . self::SEPARATOR : '') . $valuePath;
406
            }
407
408
            if (is_array($validationRules)) {
409
                $this->flattenKeys($validationRules, $resultRules, $key);
410
                continue;
411
            }
412
413
            if ($key === null) {
414
                $resultRules[] = $validationRules;
415
            } else {
416
                /** @psalm-suppress UndefinedInterfaceMethod */
417
                $resultRules[$key][] = $validationRules;
418
            }
419
        }
420
    }
421
422
    /**
423
     * Recursively checks that every item of source iterable is a valid rule instance ({@see RuleInterface}). As a
424
     * result, all iterables will be converted to arrays at the end, while single rules will be kept as is.
425
     *
426
     * @param iterable $rules Source iterable that will be checked and converted to array (so it's passed by reference).
427
     *
428
     * @psalm-param-out RawRulesType $rules
429
     *
430
     * @throws InvalidArgumentException When iterable contains items that are not rules.
431
     *
432
     * @psalm-suppress ReferenceConstraintViolation
433
     */
434
    private static function ensureArrayHasRules(iterable &$rules): void
435
    {
436
        if ($rules instanceof Traversable) {
437
            $rules = iterator_to_array($rules);
438
        }
439
440
        /** @var mixed $rule */
441
        foreach ($rules as &$rule) {
442
            if (is_iterable($rule)) {
443
                self::ensureArrayHasRules($rule);
444
            } elseif (!$rule instanceof RuleInterface) {
445
                $message = sprintf(
446
                    'Every rule must be an instance of %s, %s given.',
447
                    RuleInterface::class,
448
                    get_debug_type($rule)
449
                );
450
451
                throw new InvalidArgumentException($message);
452
            }
453
        }
454
    }
455
456
    /**
457
     * Converts rules defined with {@see EACH_SHORTCUT} to separate `Nested` and `Each` rules.
458
     *
459
     * @oaram array $rules Rules array for replacing {@see EACH_SHORTCUT} passed by reference.
460
     * @psalm-param RawRulesType $rules
461
     */
462
    private function handleEachShortcut(array &$rules): void
463
    {
464
        while (true) {
465
            $breakWhile = true;
466
            $rulesMap = [];
467
468
            foreach ($rules as $valuePath => $rule) {
469
                if ($valuePath === self::EACH_SHORTCUT) {
470
                    throw new InvalidArgumentException('Bare shortcut is prohibited. Use "Each" rule instead.');
471
                }
472
473
                $parts = StringHelper::parsePath(
474
                    (string) $valuePath,
475
                    delimiter: self::EACH_SHORTCUT,
476
                    preserveDelimiterEscaping: true
477
                );
478
                if (count($parts) === 1) {
479
                    continue;
480
                }
481
482
                /**
483
                 * Might be a bug of XDebug, because these lines are covered by tests.
484
                 *
485
                 * @see NestedTest::dataWithOtherNestedAndEach() for test cases prefixed with "withShortcut".
486
                 */
487
                // @codeCoverageIgnoreStart
488
                $breakWhile = false;
489
490
                $lastValuePath = array_pop($parts);
491
                $lastValuePath = ltrim($lastValuePath, '.');
492
                $lastValuePath = str_replace('\\' . self::EACH_SHORTCUT, self::EACH_SHORTCUT, $lastValuePath);
493
494
                $remainingValuePath = implode(self::EACH_SHORTCUT, $parts);
495
                $remainingValuePath = rtrim($remainingValuePath, self::SEPARATOR);
496
497
                if (!isset($rulesMap[$remainingValuePath])) {
498
                    $rulesMap[$remainingValuePath] = [];
499
                }
500
501
                $rulesMap[$remainingValuePath][$lastValuePath] = $rule;
502
                unset($rules[$valuePath]);
503
                // @codeCoverageIgnoreEnd
504
            }
505
506
            foreach ($rulesMap as $valuePath => $nestedRules) {
507
                /**
508
                 * Might be a bug of XDebug, because this line is covered by tests.
509
                 *
510
                 * @see NestedTest::dataWithOtherNestedAndEach() for test cases prefixed with "withShortcut".
511
                 */
512
                // @codeCoverageIgnoreStart
513
                $rules[$valuePath] = new Each([new self($nestedRules, handleEachShortcut: false)]);
514
                // @codeCoverageIgnoreEnd
515
            }
516
517
            if ($breakWhile === true) {
518
                break;
519
            }
520
        }
521
    }
522
523
    public function propagateOptions(): void
524
    {
525
        if ($this->rules === null) {
526
            return;
527
        }
528
529
        $rules = [];
530
        foreach ($this->rules as $attributeRulesIndex => $attributeRules) {
531
            $rules[$attributeRulesIndex] = is_iterable($attributeRules)
532
                ? PropagateOptionsHelper::propagate($this, $attributeRules)
533
                : PropagateOptionsHelper::propagateToRule($this, $attributeRules);
534
        }
535
536
        $this->rules = $rules;
0 ignored issues
show
Documentation Bug introduced by
It seems like $rules of type array or array is incompatible with the declared type Yiisoft\Validator\Rule\OptionalReadyRulesType of property $rules.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
537
    }
538
539
    public function afterInitAttribute(object $object, int $target): void
540
    {
541
        if ($this->rules === null) {
542
            return;
543
        }
544
545
        foreach ($this->rules as $rules) {
546
            if (is_array($rules)) {
547
                foreach ($rules as $rule) {
548
                    if ($rule instanceof AfterInitAttributeEventInterface) {
549
                        $rule->afterInitAttribute($object, $target);
550
                    }
551
                }
552
            } else {
553
                if ($rules instanceof AfterInitAttributeEventInterface) {
554
                    $rules->afterInitAttribute($object, $target);
555
                }
556
            }
557
        }
558
    }
559
560
    #[ArrayShape([
561
        'requirePropertyPath' => 'bool',
562
        'noRulesWithNoObjectMessage' => 'array',
563
        'incorrectDataSetTypeMessage' => 'array',
564
        'incorrectInputMessage' => 'array',
565
        'noPropertyPathMessage' => 'array',
566
        'skipOnEmpty' => 'bool',
567
        'skipOnError' => 'bool',
568
        'rules' => 'array|null',
569
    ])]
570
    public function getOptions(): array
571
    {
572
        return [
573
            'noRulesWithNoObjectMessage' => [
574
                'template' => $this->noRulesWithNoObjectMessage,
575
                'parameters' => [],
576
            ],
577
            'incorrectDataSetTypeMessage' => [
578
                'template' => $this->incorrectDataSetTypeMessage,
579
                'parameters' => [],
580
            ],
581
            'incorrectInputMessage' => [
582
                'template' => $this->incorrectInputMessage,
583
                'parameters' => [],
584
            ],
585
            'noPropertyPathMessage' => [
586
                'template' => $this->getNoPropertyPathMessage(),
587
                'parameters' => [],
588
            ],
589
            'requirePropertyPath' => $this->isPropertyPathRequired(),
590
            'skipOnEmpty' => $this->getSkipOnEmptyOption(),
591
            'skipOnError' => $this->skipOnError,
592
            'rules' => $this->rules === null ? null : RulesDumper::asArray($this->rules),
593
        ];
594
    }
595
596
    public function getHandler(): string
597
    {
598
        return NestedHandler::class;
599
    }
600
}
601