Passed
Push — master ( 4d0c94...12c08d )
by
unknown
02:57
created

NestedTest.php$8 ➔ dataPropagateOptions()   B

Complexity

Conditions 1

Size

Total Lines 170

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
c 0
b 0
f 0
dl 0
loc 170
rs 8

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
declare(strict_types=1);
4
5
namespace Yiisoft\Validator\Tests\Rule;
6
7
use ArrayObject;
8
use InvalidArgumentException;
9
use ReflectionProperty;
10
use stdClass;
11
use Yiisoft\Validator\DataSet\ObjectDataSet;
12
use Yiisoft\Validator\DataSetInterface;
13
use Yiisoft\Validator\Error;
14
use Yiisoft\Validator\Helper\RulesDumper;
15
use Yiisoft\Validator\Result;
16
use Yiisoft\Validator\Rule\AtLeast;
17
use Yiisoft\Validator\Rule\BooleanValue;
18
use Yiisoft\Validator\Rule\Callback;
19
use Yiisoft\Validator\Rule\Count;
20
use Yiisoft\Validator\Rule\Each;
21
use Yiisoft\Validator\Rule\Integer;
22
use Yiisoft\Validator\Rule\Length;
23
use Yiisoft\Validator\Rule\In;
24
use Yiisoft\Validator\Rule\Nested;
25
use Yiisoft\Validator\Rule\NestedHandler;
26
use Yiisoft\Validator\Rule\Number;
27
use Yiisoft\Validator\Rule\Regex;
28
use Yiisoft\Validator\Rule\Required;
29
use Yiisoft\Validator\RuleInterface;
30
use Yiisoft\Validator\RulesProviderInterface;
31
use Yiisoft\Validator\Tests\Rule\Base\DifferentRuleInHandlerTestTrait;
32
use Yiisoft\Validator\Tests\Rule\Base\RuleTestCase;
33
use Yiisoft\Validator\Tests\Rule\Base\RuleWithOptionsTestTrait;
34
use Yiisoft\Validator\Tests\Rule\Base\SkipOnErrorTestTrait;
35
use Yiisoft\Validator\Tests\Rule\Base\WhenTestTrait;
36
use Yiisoft\Validator\Tests\Support\Data\EachNestedObjects\Foo;
37
use Yiisoft\Validator\Tests\Support\Data\IteratorWithBooleanKey;
38
use Yiisoft\Validator\Tests\Support\Data\InheritAttributesObject\InheritAttributesObject;
39
use Yiisoft\Validator\Tests\Support\Data\ObjectWithDifferentPropertyVisibility;
40
use Yiisoft\Validator\Tests\Support\Data\ObjectWithNestedObject;
41
use Yiisoft\Validator\Tests\Support\Helper\OptionsHelper;
42
use Yiisoft\Validator\Tests\Support\Rule\StubRule\StubRuleWithOptions;
43
use Yiisoft\Validator\Tests\Support\RulesProvider\SimpleRulesProvider;
44
use Yiisoft\Validator\ValidationContext;
45
use Yiisoft\Validator\Validator;
46
47
use function array_slice;
48
49
final class NestedTest extends RuleTestCase
50
{
51
    use DifferentRuleInHandlerTestTrait;
52
    use RuleWithOptionsTestTrait;
53
    use SkipOnErrorTestTrait;
54
    use WhenTestTrait;
55
56
    public function testGetName(): void
57
    {
58
        $rule = new Nested();
59
60
        $this->assertSame('nested', $rule->getName());
61
    }
62
63
    public function testDefaultValues(): void
64
    {
65
        $rule = new Nested();
66
67
        $this->assertNull($rule->getRules());
68
        $this->assertSame(
69
            ReflectionProperty::IS_PRIVATE | ReflectionProperty::IS_PROTECTED | ReflectionProperty::IS_PUBLIC,
70
            $rule->getValidatedObjectPropertyVisibility(),
71
        );
72
        $this->assertFalse($rule->isPropertyPathRequired());
73
        $this->assertSame('Property "{path}" is not found.', $rule->getNoPropertyPathMessage());
74
        $this->assertNull($rule->getSkipOnEmpty());
75
        $this->assertFalse($rule->shouldSkipOnError());
76
        $this->assertNull($rule->getWhen());
77
    }
78
79
    public function testPropertyVisibilityInConstructor(): void
80
    {
81
        $rule = new Nested(validatedObjectPropertyVisibility: ReflectionProperty::IS_PRIVATE);
82
83
        $this->assertSame(ReflectionProperty::IS_PRIVATE, $rule->getValidatedObjectPropertyVisibility());
84
    }
85
86
    public function testHandlerClassName(): void
87
    {
88
        $rule = new Nested();
89
90
        $this->assertSame(NestedHandler::class, $rule->getHandler());
91
    }
92
93
    public function dataOptions(): array
94
    {
95
        return [
96
            [
97
                new Nested([new Number(pattern: '/1/')]),
98
                [
99
                    'noRulesWithNoObjectMessage' => [
100
                        'template' => 'Nested rule without rules can be used for objects only.',
101
                        'parameters' => [],
102
                    ],
103
                    'incorrectDataSetTypeMessage' => [
104
                        'template' => 'An object data set data can only have an array type.',
105
                        'parameters' => [],
106
                    ],
107
                    'incorrectInputMessage' => [
108
                        'template' => 'The value must be an array or an object.',
109
                        'parameters' => [],
110
                    ],
111
                    'noPropertyPathMessage' => [
112
                        'template' => 'Property "{path}" is not found.',
113
                        'parameters' => [],
114
                    ],
115
                    'requirePropertyPath' => false,
116
                    'skipOnEmpty' => false,
117
                    'skipOnError' => false,
118
                    'rules' => [
119
                        [
120
                            'number',
121
                            'min' => null,
122
                            'max' => null,
123
                            'incorrectInputMessage' => [
124
                                'template' => 'The allowed types are integer, float and string.',
125
                                'parameters' => [],
126
                            ],
127
                            'notNumberMessage' => [
128
                                'template' => 'Value must be a number.',
129
                                'parameters' => [],
130
                            ],
131
                            'lessThanMinMessage' => [
132
                                'template' => 'Value must be no less than {min}.',
133
                                'parameters' => ['min' => null],
134
                            ],
135
                            'greaterThanMaxMessage' => [
136
                                'template' => 'Value must be no greater than {max}.',
137
                                'parameters' => ['max' => null],
138
                            ],
139
                            'skipOnEmpty' => false,
140
                            'skipOnError' => false,
141
                            'pattern' => '/1/',
142
                        ],
143
                    ],
144
                ],
145
            ],
146
            [
147
                new Nested(['user.age' => new Number(pattern: '/1/')]),
148
                [
149
                    'noRulesWithNoObjectMessage' => [
150
                        'template' => 'Nested rule without rules can be used for objects only.',
151
                        'parameters' => [],
152
                    ],
153
                    'incorrectDataSetTypeMessage' => [
154
                        'template' => 'An object data set data can only have an array type.',
155
                        'parameters' => [],
156
                    ],
157
                    'incorrectInputMessage' => [
158
                        'template' => 'The value must be an array or an object.',
159
                        'parameters' => [],
160
                    ],
161
                    'noPropertyPathMessage' => [
162
                        'template' => 'Property "{path}" is not found.',
163
                        'parameters' => [],
164
                    ],
165
                    'requirePropertyPath' => false,
166
                    'skipOnEmpty' => false,
167
                    'skipOnError' => false,
168
                    'rules' => [
169
                        'user.age' => [
170
                            [
171
                                'number',
172
                                'min' => null,
173
                                'max' => null,
174
                                'incorrectInputMessage' => [
175
                                    'template' => 'The allowed types are integer, float and string.',
176
                                    'parameters' => [],
177
                                ],
178
                                'notNumberMessage' => [
179
                                    'template' => 'Value must be a number.',
180
                                    'parameters' => [],
181
                                ],
182
                                'lessThanMinMessage' => [
183
                                    'template' => 'Value must be no less than {min}.',
184
                                    'parameters' => ['min' => null],
185
                                ],
186
                                'greaterThanMaxMessage' => [
187
                                    'template' => 'Value must be no greater than {max}.',
188
                                    'parameters' => ['max' => null],
189
                                ],
190
                                'skipOnEmpty' => false,
191
                                'skipOnError' => false,
192
                                'pattern' => '/1/',
193
                            ],
194
                        ],
195
                    ],
196
                ],
197
            ],
198
            [
199
                new Nested([
200
                    'author.name' => new StubRuleWithOptions('author-name', ['key' => 'name']),
201
                    'author.age' => new StubRuleWithOptions('author-age', ['key' => 'age']),
202
                ]),
203
                [
204
                    'noRulesWithNoObjectMessage' => [
205
                        'template' => 'Nested rule without rules can be used for objects only.',
206
                        'parameters' => [],
207
                    ],
208
                    'incorrectDataSetTypeMessage' => [
209
                        'template' => 'An object data set data can only have an array type.',
210
                        'parameters' => [],
211
                    ],
212
                    'incorrectInputMessage' => [
213
                        'template' => 'The value must be an array or an object.',
214
                        'parameters' => [],
215
                    ],
216
                    'noPropertyPathMessage' => [
217
                        'template' => 'Property "{path}" is not found.',
218
                        'parameters' => [],
219
                    ],
220
                    'requirePropertyPath' => false,
221
                    'skipOnEmpty' => false,
222
                    'skipOnError' => false,
223
                    'rules' => [
224
                        'author.name' => [['author-name', 'key' => 'name']],
225
                        'author.age' => [['author-age', 'key' => 'age']],
226
                    ],
227
                ],
228
            ],
229
            [
230
                new Nested([
231
                    'author' => [
232
                        'name' => new StubRuleWithOptions('author-name', ['key' => 'name']),
233
                        'age' => new StubRuleWithOptions('author-age', ['key' => 'age']),
234
                    ],
235
                ]),
236
                [
237
                    'noRulesWithNoObjectMessage' => [
238
                        'template' => 'Nested rule without rules can be used for objects only.',
239
                        'parameters' => [],
240
                    ],
241
                    'incorrectDataSetTypeMessage' => [
242
                        'template' => 'An object data set data can only have an array type.',
243
                        'parameters' => [],
244
                    ],
245
                    'incorrectInputMessage' => [
246
                        'template' => 'The value must be an array or an object.',
247
                        'parameters' => [],
248
                    ],
249
                    'noPropertyPathMessage' => [
250
                        'template' => 'Property "{path}" is not found.',
251
                        'parameters' => [],
252
                    ],
253
                    'requirePropertyPath' => false,
254
                    'skipOnEmpty' => false,
255
                    'skipOnError' => false,
256
                    'rules' => [
257
                        'author.name' => [['author-name', 'key' => 'name']],
258
                        'author.age' => [['author-age', 'key' => 'age']],
259
                    ],
260
                ],
261
            ],
262
        ];
263
    }
264
265
    public function testGetOptionsWithNotRule(): void
266
    {
267
        $this->expectException(InvalidArgumentException::class);
268
269
        $ruleInterfaceName = RuleInterface::class;
270
        $message = "Every rule must be an instance of $ruleInterfaceName, class@anonymous given.";
271
        $this->expectExceptionMessage($message);
272
273
        $rule = new Nested([
274
            'a' => new Required(),
275
            'b' => new class () {
276
            },
277
            'c' => new Number(min: 1),
278
        ]);
279
        $rule->getOptions();
280
    }
281
282
    public function testValidationRuleIsNotInstanceOfRule(): void
283
    {
284
        $this->expectException(InvalidArgumentException::class);
285
        new Nested(['path.to.value' => (new stdClass())]);
286
    }
287
288
    public function testWithNestedAndEachShortcutBare(): void
289
    {
290
        $this->expectException(InvalidArgumentException::class);
291
        $this->expectExceptionMessage('Bare shortcut is prohibited. Use "Each" rule instead.');
292
        new Nested(['*' => [new Number(min: -10, max: 10)]]);
293
    }
294
295
    public function dataHandler(): array
296
    {
297
        return [
298
            'class-string-rules' => [
299
                new class () {
300
                    #[Nested(ObjectWithDifferentPropertyVisibility::class)]
301
                    private array $array = [
0 ignored issues
show
introduced by
The private property $array is not used, and could be removed.
Loading history...
302
                        'name' => 'hello',
303
                        'age' => 17,
304
                        'number' => 500,
305
                    ];
306
                },
307
                [
308
                    'array.age' => ['Value must be no less than 21.'],
309
                    'array.number' => ['Value must be no greater than 100.'],
310
                ],
311
            ],
312
            'class-string-rules-private-only' => [
313
                new class () {
314
                    #[Nested(
315
                        rules: ObjectWithDifferentPropertyVisibility::class,
316
                        rulesSourceClassPropertyVisibility: ReflectionProperty::IS_PRIVATE,
317
                    )]
318
                    private array $array = [
319
                        'name' => 'hello',
320
                        'age' => 17,
321
                        'number' => 500,
322
                    ];
323
                },
324
                [
325
                    'array.number' => ['Value must be no greater than 100.'],
326
                ],
327
            ],
328
            'rules-provider' => [
329
                new class () implements RulesProviderInterface {
330
                    private array $array = [
331
                        'name' => 'hello',
332
                        'age' => 17,
333
                        'number' => 500,
334
                    ];
335
336
                    public function getRules(): iterable
337
                    {
338
                        return [
339
                            'array' => new Nested(
340
                                new SimpleRulesProvider([
341
                                    'age' => new Number(min: 99),
342
                                ])
343
                            ),
344
                        ];
345
                    }
346
                },
347
                [
348
                    'array.age' => ['Value must be no less than 99.'],
349
                ],
350
            ],
351
            'empty-rules' => [
352
                new class () {
353
                    #[Nested([])]
354
                    private ObjectWithDifferentPropertyVisibility $object;
355
356
                    public function __construct()
357
                    {
358
                        $this->object = new ObjectWithDifferentPropertyVisibility();
359
                    }
360
                },
361
                [],
362
            ],
363
            'rules-from-validated-value' => [
364
                new class () {
365
                    #[Nested]
366
                    private ObjectWithDifferentPropertyVisibility $object;
367
368
                    public function __construct()
369
                    {
370
                        $this->object = new ObjectWithDifferentPropertyVisibility();
371
                    }
372
                },
373
                [
374
                    'object.name' => ['Value cannot be blank.'],
375
                    'object.age' => ['Value must be no less than 21.'],
376
                ],
377
            ],
378
            'rules-from-validated-value-only-public' => [
379
                new class () {
380
                    #[Nested(validatedObjectPropertyVisibility: ReflectionProperty::IS_PUBLIC)]
381
                    private ObjectWithDifferentPropertyVisibility $object;
382
383
                    public function __construct()
384
                    {
385
                        $this->object = new ObjectWithDifferentPropertyVisibility();
386
                    }
387
                },
388
                [
389
                    'object.name' => ['Value cannot be blank.'],
390
                ],
391
            ],
392
            'rules-from-validated-value-only-protected' => [
393
                new class () {
394
                    #[Nested(validatedObjectPropertyVisibility: ReflectionProperty::IS_PROTECTED)]
395
                    private ObjectWithDifferentPropertyVisibility $object;
396
397
                    public function __construct()
398
                    {
399
                        $this->object = new ObjectWithDifferentPropertyVisibility();
400
                    }
401
                },
402
                [
403
                    'object.age' => ['Value must be no less than 21.'],
404
                ],
405
            ],
406
            'rules-from-validated-value-inherit-attributes' => [
407
                new class () {
408
                    #[Nested]
409
                    private InheritAttributesObject $object;
410
411
                    public function __construct()
412
                    {
413
                        $this->object = new InheritAttributesObject();
414
                    }
415
                },
416
                [
417
                    'object.age' => [
418
                        'Value must be no less than 21.',
419
                        'Value must be equal to "23".',
420
                    ],
421
                    'object.number' => ['Value must be equal to "99".'],
422
                ],
423
            ],
424
            'nested-with-each' => [
425
                new Foo(),
426
                [
427
                    'name' => ['Value cannot be blank.'],
428
                    'bars.0.name' => ['Value cannot be blank.'],
429
                ],
430
            ],
431
        ];
432
    }
433
434
    /**
435
     * @dataProvider dataHandler
436
     */
437
    public function testHandler(object $data, array $expectedErrorMessagesIndexedByPath): void
438
    {
439
        $result = (new Validator())->validate($data);
440
        $this->assertSame($expectedErrorMessagesIndexedByPath, $result->getErrorMessagesIndexedByPath());
441
    }
442
443
    public function dataPropagateOptions(): array
444
    {
445
        return [
446
            'nested and each combinations' => [
447
                new Nested(
448
                    [
449
                        'posts' => [
450
                            new Each([
451
                                new Nested([
452
                                    'title' => [new Length(min: 3)],
453
                                    'authors' => [
454
                                        new Each([
455
                                            new Nested([
456
                                                'data' => [
457
                                                    'name' => [new Length(min: 5)],
458
                                                    'age' => [
459
                                                        new Number(min: 18),
460
                                                        new Number(min: 20),
461
                                                    ],
462
                                                ],
463
                                            ]),
464
                                        ]),
465
                                    ],
466
                                ]),
467
                            ]),
468
                        ],
469
                        'meta' => [new Length(min: 7)],
470
                    ],
471
                    propagateOptions: true,
472
                    skipOnEmpty: true,
473
                    skipOnError: true,
474
                ),
475
                [
476
                    [
477
                        'nested',
478
                        'skipOnEmpty' => true,
479
                        'skipOnError' => true,
480
                        'rules' => [
481
                            'posts' => [
482
                                [
483
                                    'each',
484
                                    'skipOnEmpty' => true,
485
                                    'skipOnError' => true,
486
                                    'rules' => [
487
                                        [
488
                                            [
489
                                                'nested',
490
                                                'skipOnEmpty' => true,
491
                                                'skipOnError' => true,
492
                                                'rules' => [
493
                                                    'title' => [
494
                                                        [
495
                                                            'length',
496
                                                            'skipOnEmpty' => true,
497
                                                            'skipOnError' => true,
498
                                                        ],
499
                                                    ],
500
                                                    'authors' => [
501
                                                        [
502
                                                            'each',
503
                                                            'skipOnEmpty' => true,
504
                                                            'skipOnError' => true,
505
                                                            'rules' => [
506
                                                                [
507
                                                                    [
508
                                                                        'nested',
509
                                                                        'skipOnEmpty' => true,
510
                                                                        'skipOnError' => true,
511
                                                                        'rules' => [
512
                                                                            'data.name' => [
513
                                                                                [
514
                                                                                    'length',
515
                                                                                    'skipOnEmpty' => true,
516
                                                                                    'skipOnError' => true,
517
                                                                                ],
518
                                                                            ],
519
                                                                            'data.age' => [
520
                                                                                [
521
                                                                                    'number',
522
                                                                                    'skipOnEmpty' => true,
523
                                                                                    'skipOnError' => true,
524
                                                                                ],
525
                                                                                [
526
                                                                                    'number',
527
                                                                                    'skipOnEmpty' => true,
528
                                                                                    'skipOnError' => true,
529
                                                                                ],
530
                                                                            ],
531
                                                                        ],
532
                                                                    ],
533
                                                                ],
534
                                                            ],
535
                                                        ],
536
                                                    ],
537
                                                ],
538
                                            ],
539
                                        ],
540
                                    ],
541
                                ],
542
                            ],
543
                            'meta' => [
544
                                [
545
                                    'length',
546
                                    'skipOnEmpty' => true,
547
                                    'skipOnError' => true,
548
                                ],
549
                            ],
550
                        ],
551
                    ],
552
                ],
553
            ],
554
            'null as rules' => [
555
                new Nested(propagateOptions: true),
556
                [
557
                    [
558
                        'nested',
559
                        'skipOnEmpty' => false,
560
                        'skipOnError' => false,
561
                        'rules' => null,
562
                    ],
563
                ],
564
            ],
565
            'single rule as integer attribute rules' => [
566
                new Nested(
567
                    [new AtLeast(['a'])],
568
                    propagateOptions: true,
569
                    skipOnEmpty: true,
570
                    skipOnError: true,
571
                ),
572
                [
573
                    [
574
                        'nested',
575
                        'skipOnEmpty' => true,
576
                        'skipOnError' => true,
577
                        'rules' => [
578
                            [
579
                                'atLeast',
580
                                'skipOnEmpty' => true,
581
                                'skipOnError' => true,
582
                            ],
583
                        ],
584
                    ],
585
                ],
586
            ],
587
            'single rule as string attribute rules' => [
588
                new Nested(
589
                    [
590
                        'numbers' => new Each(new Number()),
591
                    ],
592
                    propagateOptions: true,
593
                    skipOnEmpty: true,
594
                    skipOnError: true,
595
                ),
596
                [
597
                    [
598
                        'nested',
599
                        'skipOnEmpty' => true,
600
                        'skipOnError' => true,
601
                        'rules' => [
602
                            'numbers' => [
603
                                [
604
                                    'each',
605
                                    'skipOnEmpty' => true,
606
                                    'skipOnError' => true,
607
                                    'rules' => [
608
                                        [
609
                                            [
610
                                                'number',
611
                                                'skipOnEmpty' => true,
612
                                                'skipOnError' => true,
613
                                            ],
614
                                        ],
615
                                    ],
616
                                ],
617
                            ],
618
                        ],
619
                    ],
620
                ],
621
            ],
622
        ];
623
    }
624
625
    /**
626
     * @dataProvider dataPropagateOptions
627
     */
628
    public function testPropagateOptions(Nested $rule, array $expectedOptions): void
629
    {
630
        $options = RulesDumper::asArray([$rule]);
631
        OptionsHelper::filterRecursive($options, ['skipOnEmpty', 'skipOnError', 'rules']);
632
        $this->assertSame($expectedOptions, $options);
633
    }
634
635
    public function testNestedWithoutRulesWithObject(): void
636
    {
637
        $validator = new Validator();
638
        $result = $validator->validate(new ObjectWithNestedObject());
639
640
        $this->assertFalse($result->isValid());
641
        $this->assertSame(
642
            [
643
                'caption' => ['This value must contain at least 3 characters.'],
644
                'object.name' => ['This value must contain at least 5 characters.'],
645
            ],
646
            $result->getErrorMessagesIndexedByPath()
647
        );
648
    }
649
650
    public function dataWithOtherNestedAndEach(): array
651
    {
652
        $data = [
653
            'charts' => [
654
                [
655
                    'points' => [
656
                        ['coordinates' => ['x' => -11, 'y' => 11], 'rgb' => [-1, 256, 0]],
657
                        ['coordinates' => ['x' => -12, 'y' => 12], 'rgb' => [0, -2, 257]],
658
                    ],
659
                ],
660
                [
661
                    'points' => [
662
                        ['coordinates' => ['x' => -1, 'y' => 1], 'rgb' => [0, 0, 0]],
663
                        ['coordinates' => ['x' => -2, 'y' => 2], 'rgb' => [255, 255, 255]],
664
                    ],
665
                ],
666
                [
667
                    'points' => [
668
                        ['coordinates' => ['x' => -13, 'y' => 13], 'rgb' => [-3, 258, 0]],
669
                        ['coordinates' => ['x' => -14, 'y' => 14], 'rgb' => [0, -4, 259]],
670
                    ],
671
                ],
672
            ],
673
        ];
674
        $xRules = [
675
            new Number(min: -10, max: 10),
676
            new Callback(static function (mixed $value, object $rule, ValidationContext $context): Result {
0 ignored issues
show
Unused Code introduced by
The parameter $rule is not used and could be removed. ( Ignorable by Annotation )

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

676
            new Callback(static function (mixed $value, /** @scrutinizer ignore-unused */ object $rule, ValidationContext $context): Result {

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Unused Code introduced by
The parameter $context is not used and could be removed. ( Ignorable by Annotation )

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

676
            new Callback(static function (mixed $value, object $rule, /** @scrutinizer ignore-unused */ ValidationContext $context): Result {

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Unused Code introduced by
The parameter $value is not used and could be removed. ( Ignorable by Annotation )

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

676
            new Callback(static function (/** @scrutinizer ignore-unused */ mixed $value, object $rule, ValidationContext $context): Result {

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
677
                $result = new Result();
678
                $result->addError('Custom error.');
679
680
                return $result;
681
            }),
682
        ];
683
        $yRules = [new Number(min: -10, max: 10)];
684
        $rgbRules = [
685
            new Count(exactly: 3),
686
            new Each([new Number(min: 0, max: 255)]),
687
        ];
688
689
        $detailedErrorsData = [
690
            [['charts', 0, 'points', 0, 'coordinates', 'x'], 'Value must be no less than -10.'],
691
            [['charts', 0, 'points', 0, 'coordinates', 'x'], 'Custom error.'],
692
            [['charts', 0, 'points', 0, 'coordinates', 'y'], 'Value must be no greater than 10.'],
693
            [['charts', 0, 'points', 0, 'rgb', 0], 'Value must be no less than 0.'],
694
            [['charts', 0, 'points', 0, 'rgb', 1], 'Value must be no greater than 255.'],
695
            [['charts', 0, 'points', 1, 'coordinates', 'x'], 'Value must be no less than -10.'],
696
            [['charts', 0, 'points', 1, 'coordinates', 'x'], 'Custom error.'],
697
            [['charts', 0, 'points', 1, 'coordinates', 'y'], 'Value must be no greater than 10.'],
698
            [['charts', 0, 'points', 1, 'rgb', 1], 'Value must be no less than 0.'],
699
            [['charts', 0, 'points', 1, 'rgb', 2], 'Value must be no greater than 255.'],
700
            [['charts', 1, 'points', 0, 'coordinates', 'x'], 'Custom error.'],
701
            [['charts', 1, 'points', 1, 'coordinates', 'x'], 'Custom error.'],
702
            [['charts', 2, 'points', 0, 'coordinates', 'x'], 'Value must be no less than -10.'],
703
            [['charts', 2, 'points', 0, 'coordinates', 'x'], 'Custom error.'],
704
            [['charts', 2, 'points', 0, 'coordinates', 'y'], 'Value must be no greater than 10.'],
705
            [['charts', 2, 'points', 0, 'rgb', 0], 'Value must be no less than 0.'],
706
            [['charts', 2, 'points', 0, 'rgb', 1], 'Value must be no greater than 255.'],
707
            [['charts', 2, 'points', 1, 'coordinates', 'x'], 'Value must be no less than -10.'],
708
            [['charts', 2, 'points', 1, 'coordinates', 'x'], 'Custom error.'],
709
            [['charts', 2, 'points', 1, 'coordinates', 'y'], 'Value must be no greater than 10.'],
710
            [['charts', 2, 'points', 1, 'rgb', 1], 'Value must be no less than 0.'],
711
            [['charts', 2, 'points', 1, 'rgb', 2], 'Value must be no greater than 255.'],
712
        ];
713
        $detailedErrors = [];
714
        foreach ($detailedErrorsData as $errorData) {
715
            $detailedErrors[] = [$errorData[1], $errorData[0]];
716
        }
717
718
        $errorMessages = [
719
            'Value must be no less than -10.',
720
            'Custom error.',
721
            'Value must be no greater than 10.',
722
            'Value must be no less than 0.',
723
            'Value must be no greater than 255.',
724
            'Value must be no less than -10.',
725
            'Custom error.',
726
            'Value must be no greater than 10.',
727
            'Value must be no less than 0.',
728
            'Value must be no greater than 255.',
729
            'Custom error.',
730
            'Custom error.',
731
            'Value must be no less than -10.',
732
            'Custom error.',
733
            'Value must be no greater than 10.',
734
            'Value must be no less than 0.',
735
            'Value must be no greater than 255.',
736
            'Value must be no less than -10.',
737
            'Custom error.',
738
            'Value must be no greater than 10.',
739
            'Value must be no less than 0.',
740
            'Value must be no greater than 255.',
741
        ];
742
        $errorMessagesIndexedByPath = [
743
            'charts.0.points.0.coordinates.x' => ['Value must be no less than -10.', 'Custom error.'],
744
            'charts.0.points.0.coordinates.y' => ['Value must be no greater than 10.'],
745
            'charts.0.points.0.rgb.0' => ['Value must be no less than 0.'],
746
            'charts.0.points.0.rgb.1' => ['Value must be no greater than 255.'],
747
            'charts.0.points.1.coordinates.x' => ['Value must be no less than -10.', 'Custom error.'],
748
            'charts.0.points.1.coordinates.y' => ['Value must be no greater than 10.'],
749
            'charts.0.points.1.rgb.1' => ['Value must be no less than 0.'],
750
            'charts.0.points.1.rgb.2' => ['Value must be no greater than 255.'],
751
            'charts.1.points.0.coordinates.x' => ['Custom error.'],
752
            'charts.1.points.1.coordinates.x' => ['Custom error.'],
753
            'charts.2.points.0.coordinates.x' => ['Value must be no less than -10.', 'Custom error.'],
754
            'charts.2.points.0.coordinates.y' => ['Value must be no greater than 10.'],
755
            'charts.2.points.0.rgb.0' => ['Value must be no less than 0.'],
756
            'charts.2.points.0.rgb.1' => ['Value must be no greater than 255.'],
757
            'charts.2.points.1.coordinates.x' => ['Value must be no less than -10.', 'Custom error.'],
758
            'charts.2.points.1.coordinates.y' => ['Value must be no greater than 10.'],
759
            'charts.2.points.1.rgb.1' => ['Value must be no less than 0.'],
760
            'charts.2.points.1.rgb.2' => ['Value must be no greater than 255.'],
761
        ];
762
763
        return [
764
            'base' => [
765
                $data,
766
                [
767
                    new Nested([
768
                        'charts' => [
769
                            new Each([
770
                                new Nested([
771
                                    'points' => [
772
                                        new Each([
773
                                            new Nested([
774
                                                'coordinates' => new Nested([
775
                                                    'x' => $xRules,
776
                                                    'y' => $yRules,
777
                                                ]),
778
                                                'rgb' => $rgbRules,
779
                                            ]),
780
                                        ]),
781
                                    ],
782
                                ]),
783
                            ]),
784
                        ],
785
                    ]),
786
                ],
787
                $detailedErrors,
788
                $errorMessages,
789
                $errorMessagesIndexedByPath,
790
            ],
791
            // https://github.com/yiisoft/validator/issues/195
792
            'withShortcut' => [
793
                $data,
794
                [
795
                    new Nested([
796
                        'charts.*.points.*.coordinates.x' => $xRules,
797
                        'charts.*.points.*.coordinates.y' => $yRules,
798
                        'charts.*.points.*.rgb' => $rgbRules,
799
                    ]),
800
                ],
801
                $detailedErrors,
802
                $errorMessages,
803
                $errorMessagesIndexedByPath,
804
            ],
805
            'withShortcutAndWithoutShortcut' => [
806
                array_merge($data, ['active' => true]),
807
                [
808
                    new Nested([
809
                        'charts.*.points.*.coordinates.x' => $xRules,
810
                        'charts.*.points.*.coordinates.y' => $yRules,
811
                        'charts.*.points.*.rgb' => $rgbRules,
812
                        'active' => new BooleanValue(),
813
                    ]),
814
                ],
815
                $detailedErrors,
816
                $errorMessages,
817
                $errorMessagesIndexedByPath,
818
            ],
819
            'withShortcutAndGrouping' => [
820
                $data,
821
                [
822
                    new Nested([
823
                        'charts.*.points.*.coordinates' => new Nested([
824
                            'x' => $xRules,
825
                            'y' => $yRules,
826
                        ]),
827
                        'charts.*.points.*.rgb' => $rgbRules,
828
                    ]),
829
                ],
830
                $detailedErrors,
831
                $errorMessages,
832
                $errorMessagesIndexedByPath,
833
            ],
834
            'withShortcutAndKeysContainingSeparatorAndShortcut' => [
835
                [
836
                    'charts.list' => [
837
                        [
838
                            'points*list' => [
839
                                [
840
                                    'coordinates.data' => ['x' => -11, 'y' => 11],
841
                                    'rgb' => [-1, 256, 0],
842
                                ],
843
                            ],
844
                        ],
845
                    ],
846
                ],
847
                [
848
                    new Nested([
849
                        'charts\.list.*.points\*list.*.coordinates\.data.x' => $xRules,
850
                        'charts\.list.*.points\*list.*.coordinates\.data.y' => $yRules,
851
                        'charts\.list.*.points\*list.*.rgb' => $rgbRules,
852
                    ]),
853
                ],
854
                [
855
                    [
856
                        $errorMessages[0],
857
                        ['charts.list', 0, 'points*list', 0, 'coordinates.data', 'x'],
858
                    ],
859
                    [
860
                        $errorMessages[1],
861
                        ['charts.list', 0, 'points*list', 0, 'coordinates.data', 'x'],
862
                    ],
863
                    [
864
                        $errorMessages[2],
865
                        ['charts.list', 0, 'points*list', 0, 'coordinates.data', 'y'],
866
                    ],
867
                    [
868
                        $errorMessages[3],
869
                        ['charts.list', 0, 'points*list', 0, 'rgb', 0],
870
                    ],
871
                    [
872
                        $errorMessages[4],
873
                        ['charts.list', 0, 'points*list', 0, 'rgb', 1],
874
                    ],
875
                ],
876
                array_slice($errorMessages, 0, 5),
877
                [
878
                    'charts\.list.0.points\*list.0.coordinates\.data.x' => [$errorMessages[0], $errorMessages[1]],
879
                    'charts\.list.0.points\*list.0.coordinates\.data.y' => [$errorMessages[2]],
880
                    'charts\.list.0.points\*list.0.rgb.0' => [$errorMessages[3]],
881
                    'charts\.list.0.points\*list.0.rgb.1' => [$errorMessages[4]],
882
                ],
883
            ],
884
        ];
885
    }
886
887
    /**
888
     * @dataProvider dataWithOtherNestedAndEach
889
     */
890
    public function testWithOtherNestedAndEach(
891
        mixed $data,
892
        array $rules,
893
        array $expectedDetailedErrors,
894
        array $expectedErrorMessages,
895
        array $expectedErrorMessagesIndexedByPath
896
    ): void {
897
        $result = (new Validator())->validate($data, $rules);
898
899
        $errorsData = array_map(
900
            static fn (Error $error) => [
901
                $error->getMessage(),
902
                $error->getValuePath(),
903
            ],
904
            $result->getErrors()
905
        );
906
907
        $this->assertSame($expectedDetailedErrors, $errorsData);
908
        $this->assertSame($expectedErrorMessages, $result->getErrorMessages());
909
        $this->assertSame($expectedErrorMessagesIndexedByPath, $result->getErrorMessagesIndexedByPath());
910
    }
911
912
    public function dataValidationPassed(): array
913
    {
914
        return [
915
            [
916
                [
917
                    'author' => [
918
                        'name' => 'Dmitry',
919
                        'age' => 18,
920
                    ],
921
                ],
922
                [
923
                    new Nested([
924
                        'author.name' => [
925
                            new Length(min: 3),
926
                        ],
927
                    ]),
928
                ],
929
            ],
930
            [
931
                [
932
                    'author' => [
933
                        'name' => 'Dmitry',
934
                        'age' => 18,
935
                    ],
936
                ],
937
                [
938
                    new Nested([
939
                        'author' => [
940
                            new Required(),
941
                            new Nested([
942
                                'name' => [new Length(min: 3)],
943
                            ]),
944
                        ],
945
                    ]),
946
                ],
947
            ],
948
            'key not exists, skip empty' => [
949
                [
950
                    'author' => [
951
                        'name' => 'Dmitry',
952
                        'age' => 18,
953
                    ],
954
                ],
955
                [new Nested(['author.sex' => [new In(['male', 'female'], skipOnEmpty: true)]])],
956
            ],
957
            'keys containing separator, one nested rule' => [
958
                [
959
                    'author.data' => [
960
                        'name.surname' => 'Dmitriy',
961
                    ],
962
                ],
963
                [
964
                    new Nested([
965
                        'author\.data.name\.surname' => [
966
                            new Length(min: 3),
967
                        ],
968
                    ]),
969
                ],
970
            ],
971
            'keys containing separator, multiple nested rules' => [
972
                [
973
                    'author.data' => [
974
                        'name.surname' => 'Dmitriy',
975
                    ],
976
                ],
977
                [
978
                    new Nested([
979
                        'author\.data' => new Nested([
980
                            'name\.surname' => [
981
                                new Length(min: 3),
982
                            ],
983
                        ]),
984
                    ]),
985
                ],
986
            ],
987
            'property path of non-integer and non-string type, array' => [
988
                [0 => 'a', 1 => 'b'],
989
                [new Nested([false => new Length(min: 1), true => new Length(min: 1)])],
990
            ],
991
            'property path of non-integer and non-string type, iterator' => [
992
                [0 => 'a', 1 => 'b'],
993
                [new Nested(new IteratorWithBooleanKey())],
994
            ],
995
            'property path of non-integer and non-string type, generator' => [
996
                [0 => 'a', 1 => 'b'],
997
                [
998
                    new Nested(
999
                        new class () implements RulesProviderInterface {
1000
                            public function getRules(): iterable
1001
                            {
1002
                                yield false => new Length(min: 1);
1003
                                yield true => new Length(min: 1);
1004
                            }
1005
                        },
1006
                    ),
1007
                ],
1008
            ],
1009
            'iterator in rules' => [
1010
                ['user' => ['age' => 19]],
1011
                [new Nested(new ArrayObject(['user.age' => new Number(min: 18)]))],
1012
            ],
1013
        ];
1014
    }
1015
1016
    public function dataValidationFailed(): array
1017
    {
1018
        $incorrectDataSet = new class () implements DataSetInterface {
1019
            public function getAttributeValue(string $attribute): mixed
1020
            {
1021
                return false;
1022
            }
1023
1024
            public function getData(): ?array
1025
            {
1026
                return null;
1027
            }
1028
1029
            public function hasAttribute(string $attribute): bool
1030
            {
1031
                return false;
1032
            }
1033
        };
1034
1035
        return [
1036
            // No rules with no object
1037
            'no rules with no object, array' => [
1038
                new class () {
1039
                    #[Nested]
1040
                    public array $value = [];
1041
                },
1042
                null,
1043
                ['value' => ['Nested rule without rules can be used for objects only.']],
1044
            ],
1045
            'no rules with no object, boolean' => [
1046
                new class () {
1047
                    #[Nested]
1048
                    public bool $value = false;
1049
                },
1050
                null,
1051
                ['value' => ['Nested rule without rules can be used for objects only.']],
1052
            ],
1053
            'no rules with no object, integer' => [
1054
                new class () {
1055
                    #[Nested]
1056
                    public int $value = 42;
1057
                },
1058
                null,
1059
                ['value' => ['Nested rule without rules can be used for objects only.']],
1060
            ],
1061
            'custom no rules with no object message' => [
1062
                new class () {
1063
                    #[Nested(noRulesWithNoObjectMessage: 'Custom no rules with no object message.')]
1064
                    public array $value = [];
1065
                },
1066
                null,
1067
                ['value' => ['Custom no rules with no object message.']],
1068
            ],
1069
            'custom no rules with no object message with parameters' => [
1070
                new class () {
1071
                    #[Nested(noRulesWithNoObjectMessage: 'Attribute - {attribute}, type - {type}.')]
1072
                    public array $value = [];
1073
                },
1074
                null,
1075
                ['value' => ['Attribute - value, type - array.']],
1076
            ],
1077
            // Incorrect data set type
1078
            'incorrect data set type' => [
1079
                $incorrectDataSet,
1080
                [new Nested(['value' => new Required()])],
1081
                ['' => ['An object data set data can only have an array type.']],
1082
            ],
1083
            'custom incorrect data set type message' => [
1084
                $incorrectDataSet,
1085
                [
1086
                    new Nested(
1087
                        ['value' => new Required()],
1088
                        incorrectDataSetTypeMessage: 'Custom incorrect data set type message.',
1089
                    ),
1090
                ],
1091
                ['' => ['Custom incorrect data set type message.']],
1092
            ],
1093
            'custom incorrect data set type message with parameters' => [
1094
                $incorrectDataSet,
1095
                [new Nested(['value' => new Required()], incorrectDataSetTypeMessage: 'Type - {type}.')],
1096
                ['' => ['Type - null.']],
1097
            ],
1098
            // Incorrect input
1099
            'incorrect input' => [
1100
                '',
1101
                [new Nested(['value' => new Required()])],
1102
                ['' => ['The value must be an array or an object.']],
1103
            ],
1104
            'custom incorrect input message' => [
1105
                '',
1106
                [new Nested(['value' => new Required()], incorrectInputMessage: 'Custom incorrect input message.')],
1107
                ['' => ['Custom incorrect input message.']],
1108
            ],
1109
            'custom incorrect input message with parameters' => [
1110
                '',
1111
                [
1112
                    new Nested(
1113
                        ['value' => new Required()],
1114
                        incorrectInputMessage: 'Attribute - {attribute}, type - {type}.',
1115
                    ),
1116
                ],
1117
                ['' => ['Attribute - , type - string.']],
1118
            ],
1119
            'custom incorrect input message with parameters, attribute set' => [
1120
                ['data' => ''],
1121
                [
1122
                    'data' => new Nested(
1123
                        ['value' => new Required()],
1124
                        incorrectInputMessage: 'Attribute - {attribute}, type - {type}.',
1125
                    ),
1126
                ],
1127
                ['data' => ['Attribute - data, type - string.']],
1128
            ],
1129
            'error' => [
1130
                [
1131
                    'author' => [
1132
                        'name' => 'Alex',
1133
                        'age' => 38,
1134
                    ],
1135
                ],
1136
                [new Nested(['author.age' => [new Number(min: 40)]])],
1137
                ['author.age' => ['Value must be no less than 40.']],
1138
            ],
1139
            'key not exists' => [
1140
                [
1141
                    'author' => [
1142
                        'name' => 'Alex',
1143
                        'age' => 38,
1144
                    ],
1145
                ],
1146
                [new Nested(['author.sex' => [new In(['male', 'female'])]])],
1147
                ['author.sex' => ['This value is not in the list of acceptable values.']],
1148
            ],
1149
            [
1150
                ['value' => null],
1151
                [new Nested(['value' => new Required()])],
1152
                ['value' => ['Value cannot be blank.']],
1153
            ],
1154
            [
1155
                [],
1156
                [new Nested(['value' => new Required()], requirePropertyPath: true)],
1157
                ['value' => ['Property "value" is not found.']],
1158
            ],
1159
            [
1160
                [],
1161
                [new Nested([0 => new Required()], requirePropertyPath: true)],
1162
                [0 => ['Property "0" is not found.']],
1163
            ],
1164
            // https://github.com/yiisoft/validator/issues/200
1165
            [
1166
                [
1167
                    'body' => [
1168
                        'shipping' => [
1169
                            'phone' => '+777777777777',
1170
                        ],
1171
                    ],
1172
                ],
1173
                [
1174
                    new Nested([
1175
                        'body.shipping' => [
1176
                            new Required(),
1177
                            new Nested([
1178
                                'phone' => [new Regex('/^\+\d{11}$/')],
1179
                            ]),
1180
                        ],
1181
                    ]),
1182
                ],
1183
                ['body.shipping.phone' => ['Value is invalid.']],
1184
            ],
1185
            [
1186
                [0 => [0 => -11]],
1187
                [
1188
                    new Nested([
1189
                        0 => new Nested([
1190
                            0 => [new Number(min: -10, max: 10)],
1191
                        ]),
1192
                    ]),
1193
                ],
1194
                ['0.0' => ['Value must be no less than -10.']],
1195
            ],
1196
            'custom error' => [
1197
                [],
1198
                [
1199
                    new Nested(
1200
                        ['value' => new Required()],
1201
                        requirePropertyPath: true,
1202
                        noPropertyPathMessage: 'Property is not found.',
1203
                    ),
1204
                ],
1205
                ['value' => ['Property is not found.']],
1206
            ],
1207
            [
1208
                new ObjectDataSet(
1209
                    new class () {
1210
                        private int $value = 7;
0 ignored issues
show
introduced by
The private property $value is not used, and could be removed.
Loading history...
1211
                    },
1212
                    ReflectionProperty::IS_PUBLIC,
1213
                ),
1214
                new Nested(['value' => new Required()]),
1215
                ['value' => ['Value cannot be blank.']],
1216
            ],
1217
            'nested context' => [
1218
                [
1219
                    'method' => 'get',
1220
                    'attributes' => ['abc' => null],
1221
                ],
1222
                [
1223
                    'method' => [new Required()],
1224
                    'attributes' => new Nested([
1225
                        'abc' => [
1226
                            new Required(when: static function (mixed $value, ValidationContext $context): bool {
1227
                                $method = $context->getGlobalDataSet()->getAttributeValue('method');
1228
                                return $method === 'get';
1229
                            }),
1230
                        ],
1231
                    ]),
1232
                ],
1233
                [
1234
                    'attributes.abc' => ['Value cannot be blank.'],
1235
                ],
1236
            ],
1237
            'deep level of nesting with plain keys' => [
1238
                [
1239
                    'level1' => [
1240
                        'level2' => [
1241
                            'level3' => [
1242
                                'key' => 7,
1243
                                'name' => 'var',
1244
                            ],
1245
                        ],
1246
                    ],
1247
                ],
1248
                new Nested([
1249
                    'level1' => [
1250
                        'level2.level3' => [
1251
                            'key' => new Integer(min: 9),
1252
                        ],
1253
                        'level2' => [
1254
                            'level3.key' => [new Integer(max: 5)],
1255
                        ],
1256
                    ],
1257
                    'level1.level2' => [
1258
                        'level3.name' => new Length(min: 5),
1259
                    ],
1260
                ]),
1261
                [
1262
                    'level1.level2.level3.key' => ['Value must be no less than 9.', 'Value must be no greater than 5.'],
1263
                    'level1.level2.level3.name' => ['This value must contain at least 5 characters.'],
1264
                ],
1265
            ],
1266
        ];
1267
    }
1268
1269
    public function dataValidationFailedWithDetailedErrors(): array
1270
    {
1271
        return [
1272
            'error' => [
1273
                [
1274
                    'author' => [
1275
                        'name' => 'Dmitry',
1276
                        'age' => 18,
1277
                    ],
1278
                ],
1279
                [new Nested(['author.age' => [new Number(min: 20)]])],
1280
                [['Value must be no less than 20.', ['author', 'age']]],
1281
            ],
1282
            'key not exists' => [
1283
                [
1284
                    'author' => [
1285
                        'name' => 'Dmitry',
1286
                        'age' => 18,
1287
                    ],
1288
                ],
1289
                [new Nested(['author.sex' => [new In(['male', 'female'])]])],
1290
                [['This value is not in the list of acceptable values.', ['author', 'sex']]],
1291
            ],
1292
            [
1293
                '',
1294
                [new Nested(['value' => new Required()])],
1295
                [['The value must be an array or an object.', []]],
1296
            ],
1297
            [
1298
                ['value' => null],
1299
                [new Nested(['value' => new Required()])],
1300
                [['Value cannot be blank.', ['value']]],
1301
            ],
1302
            [
1303
                [],
1304
                [new Nested(['value1' => new Required(), 'value2' => new Required()], requirePropertyPath: true)],
1305
                [
1306
                    ['Property "value1" is not found.', ['value1']],
1307
                    ['Property "value2" is not found.', ['value2']],
1308
                ],
1309
            ],
1310
            [
1311
                // https://github.com/yiisoft/validator/issues/200
1312
                [
1313
                    'body' => [
1314
                        'shipping' => [
1315
                            'phone' => '+777777777777',
1316
                        ],
1317
                    ],
1318
                ],
1319
                [
1320
                    new Nested([
1321
                        'body.shipping' => [
1322
                            new Required(),
1323
                            new Nested([
1324
                                'phone' => [new Regex('/^\+\d{11}$/')],
1325
                            ]),
1326
                        ],
1327
                    ]),
1328
                ],
1329
                [['Value is invalid.', ['body', 'shipping', 'phone']]],
1330
            ],
1331
            [
1332
                [0 => [0 => -11]],
1333
                [
1334
                    new Nested([
1335
                        0 => new Nested([
1336
                            0 => [new Number(min: -10, max: 10)],
1337
                        ]),
1338
                    ]),
1339
                ],
1340
                [['Value must be no less than -10.', [0, 0]]],
1341
            ],
1342
            [
1343
                [
1344
                    'author.data' => [
1345
                        'name.surname' => 'Dmitriy',
1346
                    ],
1347
                ],
1348
                [new Nested(['author\.data.name\.surname' => [new Length(min: 8)]])],
1349
                [['This value must contain at least 8 characters.', ['author.data', 'name.surname']]],
1350
            ],
1351
        ];
1352
    }
1353
1354
    /**
1355
     * @dataProvider dataValidationFailedWithDetailedErrors
1356
     */
1357
    public function testValidationFailedWithDetailedErrors(mixed $data, array $rules, array $errors): void
1358
    {
1359
        $result = (new Validator())->validate($data, $rules);
1360
1361
        $errorsData = array_map(
1362
            static fn (Error $error) => [
1363
                $error->getMessage(),
1364
                $error->getValuePath(),
1365
            ],
1366
            $result->getErrors()
1367
        );
1368
1369
        $this->assertFalse($result->isValid());
1370
        $this->assertSame($errors, $errorsData);
1371
    }
1372
1373
    public function testInitWithNotARule(): void
1374
    {
1375
        $this->expectException(InvalidArgumentException::class);
1376
        $message = 'Every rule must be an instance of Yiisoft\Validator\RuleInterface, string given.';
1377
        $this->expectExceptionMessage($message);
1378
        new Nested([
1379
            'data' => new Nested([
1380
                'title' => [new Length(max: 255)],
1381
                'active' => [new BooleanValue(), 'Not a rule'],
1382
            ]),
1383
        ]);
1384
    }
1385
1386
    public function testSkipOnError(): void
1387
    {
1388
        $this->testSkipOnErrorInternal(new Nested(), new Nested(skipOnError: true));
1389
    }
1390
1391
    public function testWhen(): void
1392
    {
1393
        $when = static fn (mixed $value): bool => $value !== null;
1394
        $this->testWhenInternal(new Nested(), new Nested(when: $when));
1395
    }
1396
1397
    public function testInvalidRules(): void
1398
    {
1399
        $this->expectException(InvalidArgumentException::class);
1400
        $this->expectExceptionMessage(
1401
            'The $rules argument passed to Nested rule can be either: a null, an object implementing ' .
1402
            'RulesProviderInterface, a class string or an iterable.'
1403
        );
1404
        new Nested(new Required());
1405
    }
1406
1407
    protected function getDifferentRuleInHandlerItems(): array
1408
    {
1409
        return [Nested::class, NestedHandler::class];
1410
    }
1411
}
1412