Passed
Pull Request — master (#468)
by Sergei
03:08
created

ValidatorTest::testObjectWithAttributesOnly()

Size

Total Lines 11
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 1 Features 0
Metric Value
eloc 6
c 1
b 1
f 0
nc 1
nop 0
dl 0
loc 11
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Yiisoft\Validator\Tests;
6
7
use InvalidArgumentException;
8
use PHPUnit\Framework\TestCase;
9
use stdClass;
10
use Yiisoft\Validator\AttributeTranslator\NullAttributeTranslator;
11
use Yiisoft\Validator\DataSet\ArrayDataSet;
12
use Yiisoft\Validator\DataSet\ObjectDataSet;
13
use Yiisoft\Validator\DataSetInterface;
14
use Yiisoft\Validator\EmptyCriteria\WhenEmpty;
15
use Yiisoft\Validator\EmptyCriteria\WhenNull;
16
use Yiisoft\Validator\Error;
17
use Yiisoft\Validator\Exception\RuleHandlerInterfaceNotImplementedException;
18
use Yiisoft\Validator\Exception\RuleHandlerNotFoundException;
19
use Yiisoft\Validator\Result;
20
use Yiisoft\Validator\Rule\Boolean;
21
use Yiisoft\Validator\Rule\CompareTo;
22
use Yiisoft\Validator\Rule\HasLength;
23
use Yiisoft\Validator\Rule\In;
24
use Yiisoft\Validator\Rule\IsTrue;
25
use Yiisoft\Validator\Rule\Number;
26
use Yiisoft\Validator\Rule\Required;
27
use Yiisoft\Validator\RuleInterface;
28
use Yiisoft\Validator\RulesProviderInterface;
29
use Yiisoft\Validator\Tests\Rule\RuleWithBuiltInHandler;
30
use Yiisoft\Validator\Tests\Support\Data\EachNestedObjects\Foo;
31
use Yiisoft\Validator\Tests\Support\Data\IteratorWithBooleanKey;
32
use Yiisoft\Validator\Tests\Support\Data\ObjectWithAttributesOnly;
33
use Yiisoft\Validator\Tests\Support\Data\ObjectWithDataSet;
34
use Yiisoft\Validator\Tests\Support\Data\ObjectWithDataSetAndRulesProvider;
35
use Yiisoft\Validator\Tests\Support\Data\ObjectWithDifferentPropertyVisibility;
36
use Yiisoft\Validator\Tests\Support\Data\ObjectWithPostValidationHook;
37
use Yiisoft\Validator\Tests\Support\Data\ObjectWithRulesProvider;
38
use Yiisoft\Validator\Tests\Support\Data\SimpleForm;
39
use Yiisoft\Validator\Tests\Support\Rule\NotNullRule\NotNull;
40
use Yiisoft\Validator\Tests\Support\Rule\StubRule\StubRuleWithOptions;
41
use Yiisoft\Validator\ValidationContext;
42
use Yiisoft\Validator\Validator;
43
use Yiisoft\Validator\ValidatorInterface;
44
45
class ValidatorTest extends TestCase
46
{
47
    public function setUp(): void
48
    {
49
        ObjectWithPostValidationHook::$hookCalled = false;
50
    }
51
52
    public function testBase(): void
53
    {
54
        $validator = new Validator();
55
56
        $result = $validator->validate(new ObjectWithAttributesOnly());
57
58
        $this->assertFalse($result->isValid());
59
        $this->assertSame(
60
            ['name' => ['This value must contain at least 5 characters.']],
61
            $result->getErrorMessagesIndexedByPath()
62
        );
63
    }
64
65
    public function dataDataAndRulesCombinations(): array
66
    {
67
        return [
68
            'pure-object-and-array-of-rules' => [
69
                [
70
                    'number' => ['Value must be no less than 77.'],
71
                ],
72
                new ObjectWithDifferentPropertyVisibility(),
73
                [
74
                    'age' => new Number(max: 100),
75
                    'number' => new Number(min: 77),
76
                ],
77
            ],
78
            'pure-object-and-no-rules' => [
79
                [
80
                    'name' => ['Value cannot be blank.'],
81
                    'age' => ['Value must be no less than 21.'],
82
                ],
83
                new ObjectWithDifferentPropertyVisibility(),
84
                null,
85
            ],
86
            'dataset-object-and-array-of-rules' => [
87
                [
88
                    'key1' => ['Value must be no less than 21.'],
89
                ],
90
                new ObjectWithDataSet(),
91
                [
92
                    'key1' => new Number(min: 21),
93
                ],
94
            ],
95
            'dataset-object-and-no-rules' => [
96
                [],
97
                new ObjectWithDataSet(),
98
                null,
99
            ],
100
            'rules-provider-object-and-array-of-rules' => [
101
                [
102
                    'number' => ['Value must be no greater than 7.'],
103
                ],
104
                new ObjectWithRulesProvider(),
105
                [
106
                    'age' => new Number(max: 100),
107
                    'number' => new Number(max: 7),
108
                ],
109
            ],
110
            'rules-provider-object-and-no-rules' => [
111
                [
112
                    'age' => ['Value must be equal to "25".'],
113
                ],
114
                new ObjectWithRulesProvider(),
115
                null,
116
            ],
117
            'rules-provider-and-dataset-object-and-array-of-rules' => [
118
                [
119
                    'key2' => ['Value must be no greater than 7.'],
120
                ],
121
                new ObjectWithDataSetAndRulesProvider(),
122
                [
123
                    'key2' => new Number(max: 7),
124
                ],
125
            ],
126
            'rules-provider-and-dataset-object-and-no-rules' => [
127
                [
128
                    'key2' => ['Value must be equal to "99".'],
129
                ],
130
                new ObjectWithDataSetAndRulesProvider(),
131
                null,
132
            ],
133
            'array-and-array-of-rules' => [
134
                [
135
                    'key2' => ['Value must be no greater than 7.'],
136
                ],
137
                ['key1' => 15, 'key2' => 99],
138
                [
139
                    'key1' => new Number(max: 100),
140
                    'key2' => new Number(max: 7),
141
                ],
142
            ],
143
            'array-and-no-rules' => [
144
                [],
145
                ['key1' => 15, 'key2' => 99],
146
                null,
147
            ],
148
            'scalar-and-array-of-rules' => [
149
                [
150
                    '' => ['Value must be no greater than 7.'],
151
                ],
152
                42,
153
                [
154
                    new Number(max: 7),
155
                ],
156
            ],
157
            'scalar-and-no-rules' => [
158
                [],
159
                42,
160
                null,
161
            ],
162
            'array-and-rules-provider' => [
163
                [
164
                    'age' => ['Value must be no less than 18.'],
165
                ],
166
                [
167
                    'age' => 17,
168
                ],
169
                new class () implements RulesProviderInterface {
170
                    public function getRules(): iterable
171
                    {
172
                        return [
173
                            'age' => [new Number(min: 18)],
174
                        ];
175
                    }
176
                },
177
            ],
178
            'array-and-object' => [
179
                [
180
                    'name' => ['Value not passed.'],
181
                    'bars' => ['Value must be array or iterable.'],
182
                ],
183
                [],
184
                new Foo(),
185
            ],
186
            'array-and-callable' => [
187
                ['' => ['test message']],
188
                [],
189
                static fn (): Result => (new Result())->addError('test message'),
190
            ],
191
        ];
192
    }
193
194
    /**
195
     * @dataProvider dataDataAndRulesCombinations
196
     */
197
    public function testDataAndRulesCombinations(
198
        array $expectedErrorMessages,
199
        mixed $data,
200
        iterable|object|callable|null $rules,
201
    ): void {
202
        $validator = new Validator();
203
        $result = $validator->validate($data, $rules);
204
        $this->assertSame($expectedErrorMessages, $result->getErrorMessagesIndexedByAttribute());
205
    }
206
207
    public function dataWithEmptyArrayOfRules(): array
208
    {
209
        return [
210
            'pure-object-and-no-rules' => [new ObjectWithDifferentPropertyVisibility()],
211
            'dataset-object-and-no-rules' => [new ObjectWithDataSet()],
212
            'rules-provider-object' => [new ObjectWithRulesProvider()],
213
            'rules-provider-and-dataset-object' => [new ObjectWithDataSetAndRulesProvider()],
214
            'array' => [['key1' => 15, 'key2' => 99]],
215
            'scalar' => [42],
216
        ];
217
    }
218
219
    /**
220
     * @dataProvider dataWithEmptyArrayOfRules
221
     */
222
    public function testWithEmptyArrayOfRules(mixed $data): void
223
    {
224
        $validator = new Validator();
225
        $result = $validator->validate($data, []);
226
227
        $this->assertTrue($result->isValid());
228
    }
229
230
    public function testAddingRulesViaConstructor(): void
231
    {
232
        $dataObject = new ArrayDataSet(['bool' => true, 'int' => 41]);
233
        $validator = new Validator();
234
        $result = $validator->validate($dataObject, [
235
            'bool' => [new Boolean()],
236
            'int' => [
237
                new Number(asInteger: true),
238
                new Number(asInteger: true, min: 44),
239
                static function (mixed $value): Result {
240
                    $result = new Result();
241
                    if ($value !== 42) {
242
                        $result->addError('Value should be 42!', ['int']);
243
                    }
244
245
                    return $result;
246
                },
247
            ],
248
        ]);
249
250
        $this->assertTrue($result->isAttributeValid('bool'));
251
        $this->assertFalse($result->isAttributeValid('int'));
252
    }
253
254
    public function diverseTypesDataProvider(): array
255
    {
256
        $class = new stdClass();
257
        $class->property = true;
258
259
        return [
260
            'object' => [new ObjectDataSet($class, useCache: false)],
261
            'true' => [true],
262
            'non-empty-string' => ['true'],
263
            'integer' => [12345],
264
            'float' => [12.345],
265
            'false' => [false],
266
        ];
267
    }
268
269
    /**
270
     * @dataProvider diverseTypesDataProvider
271
     */
272
    public function testDiverseTypes($dataSet): void
273
    {
274
        $validator = new Validator();
275
        $result = $validator->validate($dataSet, [new Required()]);
276
277
        $this->assertTrue($result->isValid());
278
    }
279
280
    public function testNullAsDataSet(): void
281
    {
282
        $validator = new Validator();
283
        $result = $validator->validate(null, ['property' => [new CompareTo(null)]]);
284
285
        $this->assertTrue($result->isValid());
286
    }
287
288
    public function testPreValidation(): void
289
    {
290
        $validator = new Validator();
291
        $result = $validator->validate(
292
            new ArrayDataSet(['property' => '']),
293
            ['property' => [new Required(when: static fn (mixed $value, ?ValidationContext $context): bool => false)]],
0 ignored issues
show
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

293
            ['property' => [new Required(when: static fn (/** @scrutinizer ignore-unused */ mixed $value, ?ValidationContext $context): bool => false)]],

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

293
            ['property' => [new Required(when: static fn (mixed $value, /** @scrutinizer ignore-unused */ ?ValidationContext $context): bool => false)]],

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...
294
        );
295
296
        $this->assertTrue($result->isValid());
297
    }
298
299
    public function testRuleHandlerWithoutImplement(): void
300
    {
301
        $ruleHandler = new class () {
302
        };
303
        $validator = new Validator();
304
305
        $this->expectException(RuleHandlerInterfaceNotImplementedException::class);
306
        $validator->validate(new ArrayDataSet(['property' => '']), [
307
            'property' => [
308
                new class ($ruleHandler) implements RuleInterface {
309
                    public function __construct(private $ruleHandler)
310
                    {
311
                    }
312
313
                    public function getName(): string
314
                    {
315
                        return 'test';
316
                    }
317
318
                    public function getHandler(): string
319
                    {
320
                        return $this->ruleHandler::class;
321
                    }
322
                },
323
            ],
324
        ]);
325
    }
326
327
    public function testRuleWithoutHandler(): void
328
    {
329
        $this->expectException(RuleHandlerNotFoundException::class);
330
331
        $validator = new Validator();
332
        $validator->validate(new ArrayDataSet(['property' => '']), [
333
            'property' => [
334
                new class () implements RuleInterface {
335
                    public function getName(): string
336
                    {
337
                        return 'test';
338
                    }
339
340
                    public function getHandler(): string
341
                    {
342
                        return 'NonExistClass';
343
                    }
344
                },
345
            ],
346
        ]);
347
    }
348
349
    public function requiredDataProvider(): array
350
    {
351
        $strictRules = [
352
            'orderBy' => [new Required()],
353
            'sort' => [
354
                new In(
355
                    ['asc', 'desc'],
356
                    skipOnEmpty: static fn (mixed $value, bool $isAttributeMissing): bool => $isAttributeMissing
357
                ),
358
            ],
359
        ];
360
        $notStrictRules = [
361
            'orderBy' => [new Required()],
362
            'sort' => [
363
                new In(
364
                    ['asc', 'desc'],
365
                    skipOnEmpty: static fn (
366
                        mixed $value,
367
                        bool $isAttributeMissing
368
                    ): bool => $isAttributeMissing || $value === ''
369
                ),
370
            ],
371
        ];
372
373
        return [
374
            [
375
                ['merchantId' => [new Required(), new Number(asInteger: true)]],
376
                new ArrayDataSet(['merchantId' => null]),
377
                [
378
                    new Error(
379
                        'Value cannot be blank.',
380
                        ['attribute' => 'merchantId'],
381
                        ['merchantId']
382
                    ),
383
                    new Error(
384
                        'The allowed types are integer, float and string.',
385
                        ['attribute' => 'merchantId', 'type' => 'null'],
386
                        ['merchantId']
387
                    ),
388
                ],
389
            ],
390
            [
391
                ['merchantId' => [new Required(), new Number(asInteger: true, skipOnError: true)]],
392
                new ArrayDataSet(['merchantId' => null]),
393
                [new Error('Value cannot be blank.', [ 'attribute' => 'merchantId'], ['merchantId'])],
394
            ],
395
            [
396
                ['merchantId' => [new Required(), new Number(asInteger: true, skipOnError: true)]],
397
                new ArrayDataSet(['merchantIdd' => 1]),
398
                [new Error('Value not passed.', ['attribute' => 'merchantId'], ['merchantId'])],
399
            ],
400
401
            [
402
                $strictRules,
403
                new ArrayDataSet(['orderBy' => 'name', 'sort' => 'asc']),
404
                [],
405
            ],
406
            [
407
                $notStrictRules,
408
                new ArrayDataSet(['orderBy' => 'name', 'sort' => 'asc']),
409
                [],
410
            ],
411
412
            [
413
                $strictRules,
414
                new ArrayDataSet(['orderBy' => 'name', 'sort' => 'desc']),
415
                [],
416
            ],
417
            [
418
                $notStrictRules,
419
                new ArrayDataSet(['orderBy' => 'name', 'sort' => 'desc']),
420
                [],
421
            ],
422
423
            [
424
                $strictRules,
425
                new ArrayDataSet(['orderBy' => 'name', 'sort' => 'up']),
426
                [new Error('This value is invalid.', ['attribute' => 'sort'], ['sort'])],
427
            ],
428
            [
429
                $notStrictRules,
430
                new ArrayDataSet(['orderBy' => 'name', 'sort' => 'up']),
431
                [new Error('This value is invalid.', ['attribute' => 'sort'], ['sort'])],
432
            ],
433
434
            [
435
                $strictRules,
436
                new ArrayDataSet(['orderBy' => 'name', 'sort' => '']),
437
                [new Error('This value is invalid.', ['attribute' => 'sort'], ['sort'])],
438
            ],
439
            [
440
                $notStrictRules,
441
                new ArrayDataSet(['orderBy' => 'name', 'sort' => '']),
442
                [],
443
            ],
444
445
            [
446
                $strictRules,
447
                new ArrayDataSet(['orderBy' => 'name']),
448
                [],
449
            ],
450
            [
451
                $notStrictRules,
452
                new ArrayDataSet(['orderBy' => 'name']),
453
                [],
454
            ],
455
456
            [
457
                $strictRules,
458
                new ArrayDataSet(['orderBy' => '']),
459
                [new Error('Value cannot be blank.', ['attribute' => 'orderBy'], ['orderBy'])],
460
            ],
461
            [
462
                $notStrictRules,
463
                new ArrayDataSet(['orderBy' => '']),
464
                [new Error('Value cannot be blank.', ['attribute' => 'orderBy'], ['orderBy'])],
465
            ],
466
467
            [
468
                $strictRules,
469
                new ArrayDataSet([]),
470
                [new Error('Value not passed.', ['attribute' => 'orderBy'], ['orderBy'])],
471
            ],
472
            [
473
                $notStrictRules,
474
                new ArrayDataSet([]),
475
                [new Error('Value not passed.', ['attribute' => 'orderBy'], ['orderBy'])],
476
            ],
477
            [
478
                [
479
                    'name' => [new Required(), new HasLength(min: 3, skipOnError: true)],
480
                    'description' => [new Required(), new HasLength(min: 5, skipOnError: true)],
481
                ],
482
                new ObjectDataSet(
483
                    new class () {
484
                        private string $title = '';
0 ignored issues
show
introduced by
The private property $title is not used, and could be removed.
Loading history...
485
                        private string $description = 'abc123';
0 ignored issues
show
introduced by
The private property $description is not used, and could be removed.
Loading history...
486
                    }
487
                ),
488
                [new Error('Value not passed.', ['attribute' => 'name'], ['name'])],
489
            ],
490
            [
491
                null,
492
                new ObjectDataSet(new ObjectWithDataSet()),
493
                [],
494
            ],
495
        ];
496
    }
497
498
    /**
499
     * @link https://github.com/yiisoft/validator/issues/173
500
     * @link https://github.com/yiisoft/validator/issues/289
501
     * @dataProvider requiredDataProvider
502
     */
503
    public function testRequired(array|null $rules, DataSetInterface $dataSet, array $expectedErrors): void
504
    {
505
        $validator = new Validator();
506
        $result = $validator->validate($dataSet, $rules);
507
        $this->assertEquals($expectedErrors, $result->getErrors());
508
    }
509
510
    public function skipOnEmptyDataProvider(): array
511
    {
512
        $validator = new Validator();
513
        $rules = [
514
            'name' => [new HasLength(min: 8)],
515
            'age' => [new Number(asInteger: true, min: 18)],
516
        ];
517
        $stringLessThanMinMessage = 'This value must contain at least 8 characters.';
518
        $incorrectNumberMessage = 'The allowed types are integer, float and string.';
519
        $intMessage = 'Value must be an integer.';
520
        $intLessThanMinMessage = 'Value must be no less than 18.';
521
522
        return [
523
            'rule / validator, skipOnEmpty: false, value not passed' => [
524
                $validator,
525
                new ArrayDataSet([
526
                    'name' => 'Dmitriy',
527
                ]),
528
                $rules,
529
                [
530
                    new Error($stringLessThanMinMessage, [
531
                        'min' => 8,
532
                        'attribute' => 'name',
533
                        'number' => 7,
534
                    ], ['name']),
535
                    new Error($incorrectNumberMessage, [
536
                        'attribute' => 'age',
537
                        'type' => 'null',
538
                    ], ['age']),
539
                ],
540
            ],
541
            'rule / validator, skipOnEmpty: false, value is empty' => [
542
                $validator,
543
                new ArrayDataSet([
544
                    'name' => 'Dmitriy',
545
                    'age' => null,
546
                ]),
547
                $rules,
548
                [
549
                    new Error($stringLessThanMinMessage, [
550
                        'min' => 8,
551
                        'attribute' => 'name',
552
                        'number' => 7,
553
                    ], ['name']),
554
                    new Error($incorrectNumberMessage, [
555
                        'attribute' => 'age',
556
                        'type' => 'null',
557
                    ], ['age']),
558
                ],
559
            ],
560
            'rule / validator, skipOnEmpty: false, value is not empty' => [
561
                $validator,
562
                new ArrayDataSet([
563
                    'name' => 'Dmitriy',
564
                    'age' => 17,
565
                ]),
566
                $rules,
567
                [
568
                    new Error($stringLessThanMinMessage, [
569
                        'min' => 8,
570
                        'attribute' => 'name',
571
                        'number' => 7,
572
                    ], ['name']),
573
                    new Error($intLessThanMinMessage, [
574
                        'min' => 18,
575
                        'attribute' => 'age',
576
                        'value' => 17,
577
                    ], ['age']),
578
                ],
579
            ],
580
581
            'rule, skipOnEmpty: true, value not passed' => [
582
                $validator,
583
                new ArrayDataSet([
584
                    'name' => 'Dmitriy',
585
                ]),
586
                [
587
                    'name' => [new HasLength(min: 8)],
588
                    'age' => [new Number(asInteger: true, min: 18, skipOnEmpty: true)],
589
                ],
590
                [
591
                    new Error($stringLessThanMinMessage, [
592
                        'min' => 8,
593
                        'attribute' => 'name',
594
                        'number' => 7,
595
                    ], ['name']),
596
                ],
597
            ],
598
            'rule, skipOnEmpty: true, value is empty (null)' => [
599
                $validator,
600
                new ArrayDataSet([
601
                    'name' => 'Dmitriy',
602
                    'age' => null,
603
                ]),
604
                [
605
                    'name' => [new HasLength(min: 8)],
606
                    'age' => [new Number(asInteger: true, min: 18, skipOnEmpty: true)],
607
                ],
608
                [
609
                    new Error($stringLessThanMinMessage, [
610
                        'min' => 8,
611
                        'attribute' => 'name',
612
                        'number' => 7,
613
                    ], ['name']),
614
                ],
615
            ],
616
            'rule, skipOnEmpty: true, value is empty (empty string after trimming), trimString is false' => [
617
                $validator,
618
                new ArrayDataSet([
619
                    'name' => ' ',
620
                    'age' => 17,
621
                ]),
622
                [
623
                    'name' => [new HasLength(min: 8, skipOnEmpty: true)],
624
                    'age' => [new Number(asInteger: true, min: 18)],
625
                ],
626
                [
627
                    new Error($stringLessThanMinMessage, [
628
                        'min' => 8,
629
                        'attribute' => 'name',
630
                        'number' => 1,
631
                    ], ['name']),
632
                    new Error($intLessThanMinMessage, [
633
                        'min' => 18,
634
                        'attribute' => 'age',
635
                        'value' => 17,
636
                    ], ['age']),
637
                ],
638
            ],
639
            'rule, skipOnEmpty: SkipOnEmpty, value is empty (empty string after trimming), trimString is true' => [
640
                $validator,
641
                new ArrayDataSet([
642
                    'name' => ' ',
643
                    'age' => 17,
644
                ]),
645
                [
646
                    'name' => [new HasLength(min: 8, skipOnEmpty: new WhenEmpty(trimString: true))],
647
                    'age' => [new Number(asInteger: true, min: 18)],
648
                ],
649
                [
650
                    new Error($intLessThanMinMessage, [
651
                        'min' => 18,
652
                        'attribute' => 'age',
653
                        'value' => 17,
654
                    ], ['age']),
655
                ],
656
            ],
657
            'rule, skipOnEmpty: true, value is not empty' => [
658
                $validator,
659
                new ArrayDataSet([
660
                    'name' => 'Dmitriy',
661
                    'age' => 17,
662
                ]),
663
                [
664
                    'name' => [new HasLength(min: 8)],
665
                    'age' => [new Number(asInteger: true, min: 18, skipOnEmpty: true)],
666
                ],
667
                [
668
                    new Error($stringLessThanMinMessage, [
669
                        'min' => 8,
670
                        'attribute' => 'name',
671
                        'number' => 7,
672
                    ], ['name']),
673
                    new Error($intLessThanMinMessage, [
674
                        'min' => 18,
675
                        'attribute' => 'age',
676
                        'value' => 17,
677
                    ], ['age']),
678
                ],
679
            ],
680
681
            'rule, skipOnEmpty: SkipOnNull, value not passed' => [
682
                $validator,
683
                new ArrayDataSet([
684
                    'name' => 'Dmitriy',
685
                ]),
686
                [
687
                    'name' => [new HasLength(min: 8)],
688
                    'age' => [new Number(asInteger: true, min: 18, skipOnEmpty: new WhenNull())],
689
                ],
690
                [
691
                    new Error($stringLessThanMinMessage, [
692
                        'min' => 8,
693
                        'attribute' => 'name',
694
                        'number' => 7,
695
                    ], ['name']),
696
                ],
697
            ],
698
            'rule, skipOnEmpty: SkipOnNull, value is empty' => [
699
                $validator,
700
                new ArrayDataSet([
701
                    'name' => 'Dmitriy',
702
                    'age' => null,
703
                ]),
704
                [
705
                    'name' => [new HasLength(min: 8)],
706
                    'age' => [new Number(asInteger: true, min: 18, skipOnEmpty: new WhenNull())],
707
                ],
708
                [
709
                    new Error($stringLessThanMinMessage, [
710
                        'min' => 8,
711
                        'attribute' => 'name',
712
                        'number' => 7,
713
                    ], ['name']),
714
                ],
715
            ],
716
            'rule, skipOnEmpty: SkipOnNull, value is not empty' => [
717
                $validator,
718
                new ArrayDataSet([
719
                    'name' => 'Dmitriy',
720
                    'age' => 17,
721
                ]),
722
                [
723
                    'name' => [new HasLength(min: 8)],
724
                    'age' => [new Number(asInteger: true, min: 18, skipOnEmpty: new WhenNull())],
725
                ],
726
                [
727
                    new Error($stringLessThanMinMessage, [
728
                        'min' => 8,
729
                        'attribute' => 'name',
730
                        'number' => 7,
731
                    ], ['name']),
732
                    new Error($intLessThanMinMessage, [
733
                        'min' => 18,
734
                        'attribute' => 'age',
735
                        'value' => 17,
736
                    ], ['age']),
737
                ],
738
            ],
739
            'rule, skipOnEmpty: SkipOnNull, value is not empty (empty string)' => [
740
                $validator,
741
                new ArrayDataSet([
742
                    'name' => 'Dmitriy',
743
                    'age' => '',
744
                ]),
745
                [
746
                    'name' => [new HasLength(min: 8)],
747
                    'age' => [new Number(asInteger: true, min: 18, skipOnEmpty: new WhenNull())],
748
                ],
749
                [
750
                    new Error($stringLessThanMinMessage, [
751
                        'min' => 8,
752
                        'attribute' => 'name',
753
                        'number' => 7,
754
                    ], ['name']),
755
                    new Error($intMessage, [
756
                        'attribute' => 'age',
757
                        'value' => '',
758
                    ], ['age']),
759
                ],
760
            ],
761
762
            'rule, skipOnEmpty: custom callback, value not passed' => [
763
                $validator,
764
                new ArrayDataSet([
765
                    'name' => 'Dmitriy',
766
                ]),
767
                [
768
                    'name' => [new HasLength(min: 8)],
769
                    'age' => [
770
                        new Number(
771
                            asInteger: true,
772
                            min: 18,
773
                            skipOnEmpty: static fn (mixed $value, bool $isAttributeMissing): bool => $value === 0
0 ignored issues
show
Unused Code introduced by
The parameter $isAttributeMissing 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

773
                            skipOnEmpty: static fn (mixed $value, /** @scrutinizer ignore-unused */ bool $isAttributeMissing): bool => $value === 0

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...
774
                        ),
775
                    ],
776
                ],
777
                [
778
                    new Error($stringLessThanMinMessage, [
779
                        'min' => 8,
780
                        'attribute' => 'name',
781
                        'number' => 7,
782
                    ], ['name']),
783
                    new Error($incorrectNumberMessage, [
784
                        'attribute' => 'age',
785
                        'type' => 'null',
786
                    ], ['age']),
787
                ],
788
            ],
789
            'rule, skipOnEmpty: custom callback, value is empty' => [
790
                $validator,
791
                new ArrayDataSet([
792
                    'name' => 'Dmitriy',
793
                    'age' => 0,
794
                ]),
795
                [
796
                    'name' => [new HasLength(min: 8)],
797
                    'age' => [
798
                        new Number(
799
                            asInteger: true,
800
                            min: 18,
801
                            skipOnEmpty: static fn (mixed $value, bool $isAttributeMissing): bool => $value === 0
0 ignored issues
show
Unused Code introduced by
The parameter $isAttributeMissing 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

801
                            skipOnEmpty: static fn (mixed $value, /** @scrutinizer ignore-unused */ bool $isAttributeMissing): bool => $value === 0

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...
802
                        ),
803
                    ],
804
                ],
805
                [
806
                    new Error($stringLessThanMinMessage, [
807
                        'min' => 8,
808
                        'attribute' => 'name',
809
                        'number' => 7,
810
                    ], ['name']),
811
                ],
812
            ],
813
            'rule, skipOnEmpty, custom callback, value is not empty' => [
814
                $validator,
815
                new ArrayDataSet([
816
                    'name' => 'Dmitriy',
817
                    'age' => 17,
818
                ]),
819
                [
820
                    'name' => [new HasLength(min: 8)],
821
                    'age' => [
822
                        new Number(
823
                            asInteger: true,
824
                            min: 18,
825
                            skipOnEmpty: static fn (mixed $value, bool $isAttributeMissing): bool => $value === 0
0 ignored issues
show
Unused Code introduced by
The parameter $isAttributeMissing 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

825
                            skipOnEmpty: static fn (mixed $value, /** @scrutinizer ignore-unused */ bool $isAttributeMissing): bool => $value === 0

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...
826
                        ),
827
                    ],
828
                ],
829
                [
830
                    new Error($stringLessThanMinMessage, [
831
                        'min' => 8,
832
                        'attribute' => 'name',
833
                        'number' => 7,
834
                    ], ['name']),
835
                    new Error($intLessThanMinMessage, [
836
                        'min' => 18,
837
                        'attribute' => 'age',
838
                        'value' => 17,
839
                    ], ['age']),
840
                ],
841
            ],
842
            'rule, skipOnEmpty, custom callback, value is not empty (null)' => [
843
                $validator,
844
                new ArrayDataSet([
845
                    'name' => 'Dmitriy',
846
                    'age' => null,
847
                ]),
848
                [
849
                    'name' => [new HasLength(min: 8)],
850
                    'age' => [
851
                        new Number(
852
                            asInteger: true,
853
                            min: 18,
854
                            skipOnEmpty: static fn (mixed $value, bool $isAttributeMissing): bool => $value === 0
0 ignored issues
show
Unused Code introduced by
The parameter $isAttributeMissing 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

854
                            skipOnEmpty: static fn (mixed $value, /** @scrutinizer ignore-unused */ bool $isAttributeMissing): bool => $value === 0

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...
855
                        ),
856
                    ],
857
                ],
858
                [
859
                    new Error($stringLessThanMinMessage, [
860
                        'min' => 8,
861
                        'attribute' => 'name',
862
                        'number' => 7,
863
                    ], ['name']),
864
                    new Error($incorrectNumberMessage, [
865
                        'attribute' => 'age',
866
                        'type' => 'null',
867
                    ], ['age']),
868
                ],
869
            ],
870
871
            'validator, skipOnEmpty: true, value not passed' => [
872
                new Validator(defaultSkipOnEmpty: true),
873
                new ArrayDataSet([
874
                    'name' => 'Dmitriy',
875
                ]),
876
                $rules,
877
                [
878
                    new Error($stringLessThanMinMessage, [
879
                        'min' => 8,
880
                        'attribute' => 'name',
881
                        'number' => 7,
882
                    ], ['name']),
883
                ],
884
            ],
885
            'validator, skipOnEmpty: true, value is empty' => [
886
                new Validator(defaultSkipOnEmpty: true),
887
                new ArrayDataSet([
888
                    'name' => 'Dmitriy',
889
                    'age' => null,
890
                ]),
891
                $rules,
892
                [
893
                    new Error($stringLessThanMinMessage, [
894
                        'min' => 8,
895
                        'attribute' => 'name',
896
                        'number' => 7,
897
                    ], ['name']),
898
                ],
899
            ],
900
            'validator, skipOnEmpty: true, value is not empty' => [
901
                new Validator(defaultSkipOnEmpty: true),
902
                new ArrayDataSet([
903
                    'name' => 'Dmitriy',
904
                    'age' => 17,
905
                ]),
906
                $rules,
907
                [
908
                    new Error($stringLessThanMinMessage, [
909
                        'min' => 8,
910
                        'attribute' => 'name',
911
                        'number' => 7,
912
                    ], ['name']),
913
                    new Error($intLessThanMinMessage, [
914
                        'min' => 18,
915
                        'attribute' => 'age',
916
                        'value' => 17,
917
                    ], ['age']),
918
                ],
919
            ],
920
921
            'validator, skipOnEmpty: SkipOnNull, value not passed' => [
922
                new Validator(defaultSkipOnEmpty: new WhenNull()),
923
                new ArrayDataSet([
924
                    'name' => 'Dmitriy',
925
                ]),
926
                $rules,
927
                [
928
                    new Error($stringLessThanMinMessage, [
929
                        'min' => 8,
930
                        'attribute' => 'name',
931
                        'number' => 7,
932
                    ], ['name']),
933
                ],
934
            ],
935
            'validator, skipOnEmpty: SkipOnNull, value is empty' => [
936
                new Validator(defaultSkipOnEmpty: new WhenNull()),
937
                new ArrayDataSet([
938
                    'name' => 'Dmitriy',
939
                    'age' => null,
940
                ]),
941
                $rules,
942
                [
943
                    new Error($stringLessThanMinMessage, [
944
                        'min' => 8,
945
                        'attribute' => 'name',
946
                        'number' => 7,
947
                    ], ['name']),
948
                ],
949
            ],
950
            'validator, skipOnEmpty: SkipOnNull, value is not empty' => [
951
                new Validator(defaultSkipOnEmpty: new WhenNull()),
952
                new ArrayDataSet([
953
                    'name' => 'Dmitriy',
954
                    'age' => 17,
955
                ]),
956
                $rules,
957
                [
958
                    new Error($stringLessThanMinMessage, [
959
                        'min' => 8,
960
                        'attribute' => 'name',
961
                        'number' => 7,
962
                    ], ['name']),
963
                    new Error($intLessThanMinMessage, [
964
                        'min' => 18,
965
                        'attribute' => 'age',
966
                        'value' => 17,
967
                    ], ['age']),
968
                ],
969
            ],
970
            'validator, skipOnEmpty: SkipOnNull, value is not empty (empty string)' => [
971
                new Validator(defaultSkipOnEmpty: new WhenNull()),
972
                new ArrayDataSet([
973
                    'name' => 'Dmitriy',
974
                    'age' => '',
975
                ]),
976
                $rules,
977
                [
978
                    new Error($stringLessThanMinMessage, [
979
                        'min' => 8,
980
                        'attribute' => 'name',
981
                        'number' => 7,
982
                    ], ['name']),
983
                    new Error($intMessage, [
984
                        'attribute' => 'age',
985
                        'value' => '',
986
                    ], ['age']),
987
                ],
988
            ],
989
990
            'validator, skipOnEmpty: custom callback, value not passed' => [
991
                new Validator(
992
                    defaultSkipOnEmpty: static fn (mixed $value, bool $isAttributeMissing): bool => $value === 0
0 ignored issues
show
Unused Code introduced by
The parameter $isAttributeMissing 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

992
                    defaultSkipOnEmpty: static fn (mixed $value, /** @scrutinizer ignore-unused */ bool $isAttributeMissing): bool => $value === 0

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...
993
                ),
994
                new ArrayDataSet([
995
                    'name' => 'Dmitriy',
996
                ]),
997
                $rules,
998
                [
999
                    new Error($stringLessThanMinMessage, [
1000
                        'min' => 8,
1001
                        'attribute' => 'name',
1002
                        'number' => 7,
1003
                    ], ['name']),
1004
                    new Error($incorrectNumberMessage, [
1005
                        'attribute' => 'age',
1006
                        'type' => 'null',
1007
                    ], ['age']),
1008
                ],
1009
            ],
1010
            'validator, skipOnEmpty: custom callback, value is empty' => [
1011
                new Validator(
1012
                    defaultSkipOnEmpty: static fn (mixed $value, bool $isAttributeMissing): bool => $value === 0
0 ignored issues
show
Unused Code introduced by
The parameter $isAttributeMissing 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

1012
                    defaultSkipOnEmpty: static fn (mixed $value, /** @scrutinizer ignore-unused */ bool $isAttributeMissing): bool => $value === 0

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...
1013
                ),
1014
                new ArrayDataSet([
1015
                    'name' => 'Dmitriy',
1016
                    'age' => 0,
1017
                ]),
1018
                $rules,
1019
                [
1020
                    new Error($stringLessThanMinMessage, [
1021
                        'min' => 8,
1022
                        'attribute' => 'name',
1023
                        'number' => 7,
1024
                    ], ['name']),
1025
                ],
1026
            ],
1027
            'validator, skipOnEmpty: custom callback, value is not empty' => [
1028
                new Validator(
1029
                    defaultSkipOnEmpty: static fn (mixed $value, bool $isAttributeMissing): bool => $value === 0
0 ignored issues
show
Unused Code introduced by
The parameter $isAttributeMissing 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

1029
                    defaultSkipOnEmpty: static fn (mixed $value, /** @scrutinizer ignore-unused */ bool $isAttributeMissing): bool => $value === 0

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...
1030
                ),
1031
                new ArrayDataSet([
1032
                    'name' => 'Dmitriy',
1033
                    'age' => 17,
1034
                ]),
1035
                $rules,
1036
                [
1037
                    new Error($stringLessThanMinMessage, [
1038
                        'min' => 8,
1039
                        'attribute' => 'name',
1040
                        'number' => 7,
1041
                    ], ['name']),
1042
                    new Error($intLessThanMinMessage, [
1043
                        'min' => 18,
1044
                        'attribute' => 'age',
1045
                        'value' => 17,
1046
                    ], ['age']),
1047
                ],
1048
            ],
1049
            'validator, skipOnEmpty: custom callback, value is not empty (null)' => [
1050
                new Validator(
1051
                    defaultSkipOnEmpty: static fn (mixed $value, bool $isAttributeMissing): bool => $value === 0
0 ignored issues
show
Unused Code introduced by
The parameter $isAttributeMissing 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

1051
                    defaultSkipOnEmpty: static fn (mixed $value, /** @scrutinizer ignore-unused */ bool $isAttributeMissing): bool => $value === 0

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...
1052
                ),
1053
                new ArrayDataSet([
1054
                    'name' => 'Dmitriy',
1055
                    'age' => null,
1056
                ]),
1057
                $rules,
1058
                [
1059
                    new Error($stringLessThanMinMessage, [
1060
                        'min' => 8,
1061
                        'attribute' => 'name',
1062
                        'number' => 7,
1063
                    ], ['name']),
1064
                    new Error($incorrectNumberMessage, [
1065
                        'attribute' => 'age',
1066
                        'type' => 'null',
1067
                    ], ['age']),
1068
                ],
1069
            ],
1070
        ];
1071
    }
1072
1073
    /**
1074
     * @param StubRuleWithOptions[] $rules
1075
     * @param Error[] $expectedErrors
1076
     *
1077
     * @dataProvider skipOnEmptyDataProvider
1078
     */
1079
    public function testSkipOnEmpty(Validator $validator, ArrayDataSet $data, array $rules, array $expectedErrors): void
1080
    {
1081
        $result = $validator->validate($data, $rules);
1082
        $this->assertEquals($expectedErrors, $result->getErrors());
1083
    }
1084
1085
    public function initSkipOnEmptyDataProvider(): array
1086
    {
1087
        return [
1088
            'null' => [
1089
                null,
1090
                new class () {
1091
                    #[Number]
1092
                    public ?string $name = null;
1093
                },
1094
                false,
1095
            ],
1096
            'false' => [
1097
                false,
1098
                new class () {
1099
                    #[Number]
1100
                    public ?string $name = null;
1101
                },
1102
                false,
1103
            ],
1104
            'true' => [
1105
                true,
1106
                new class () {
1107
                    #[Number]
1108
                    public ?string $name = null;
1109
                },
1110
                true,
1111
            ],
1112
            'callable' => [
1113
                new WhenNull(),
1114
                new class () {
1115
                    #[Number]
1116
                    public ?string $name = null;
1117
                },
1118
                true,
1119
            ],
1120
            'do-not-override-rule' => [
1121
                false,
1122
                new class () {
1123
                    #[Number(skipOnEmpty: true)]
1124
                    public string $name = '';
1125
                },
1126
                true,
1127
            ],
1128
        ];
1129
    }
1130
1131
    /**
1132
     * @dataProvider initSkipOnEmptyDataProvider
1133
     */
1134
    public function testInitSkipOnEmpty(
1135
        bool|callable|null $skipOnEmpty,
1136
        mixed $data,
1137
        bool $expectedResult,
1138
    ): void {
1139
        $validator = new Validator(defaultSkipOnEmpty: $skipOnEmpty);
1140
1141
        $result = $validator->validate($data);
1142
1143
        $this->assertSame($expectedResult, $result->isValid());
1144
    }
1145
1146
    public function testObjectWithAttributesOnly(): void
1147
    {
1148
        $object = new ObjectWithAttributesOnly();
1149
1150
        $validator = new Validator();
1151
1152
        $result = $validator->validate($object);
1153
1154
        $this->assertFalse($result->isValid());
1155
        $this->assertCount(1, $result->getErrorMessages());
1156
        $this->assertStringStartsWith('This value must contain at least', $result->getErrorMessages()[0]);
1157
    }
1158
1159
    public function testRuleWithoutSkipOnEmpty(): void
1160
    {
1161
        $validator = new Validator(defaultSkipOnEmpty: new WhenNull());
1162
1163
        $data = new class () {
1164
            #[NotNull]
1165
            public ?string $name = null;
1166
        };
1167
1168
        $result = $validator->validate($data);
1169
1170
        $this->assertFalse($result->isValid());
1171
    }
1172
1173
    public function testValidateWithSingleRule(): void
1174
    {
1175
        $result = (new Validator())->validate(3, new Number(min: 5));
1176
1177
        $this->assertFalse($result->isValid());
1178
        $this->assertSame(
1179
            ['' => ['Value must be no less than 5.']],
1180
            $result->getErrorMessagesIndexedByPath(),
1181
        );
1182
    }
1183
1184
    public function testComposition(): void
1185
    {
1186
        $validator = new class () implements ValidatorInterface {
1187
            private Validator $validator;
1188
1189
            public function __construct()
1190
            {
1191
                $this->validator = new Validator();
1192
            }
1193
1194
            public function validate(
1195
                mixed $data,
1196
                callable|iterable|object|string|null $rules = null,
1197
                ?ValidationContext $context = null
1198
            ): Result {
1199
                $context ??= new ValidationContext();
1200
1201
                $result = $this->validator->validate($data, $rules, $context);
1202
1203
                return $context->getParameter('forceSuccess') === true ? new Result() : $result;
1204
            }
1205
        };
1206
1207
        $rules = [
1208
            static function ($value, $rule, ValidationContext $context) {
1209
                $context->setParameter('forceSuccess', true);
1210
                return (new Result())->addError('fail');
1211
            },
1212
        ];
1213
1214
        $result = $validator->validate([], $rules);
1215
1216
        $this->assertTrue($result->isValid());
1217
    }
1218
1219
    public function testRulesWithWrongKey(): void
1220
    {
1221
        $validator = new Validator();
1222
1223
        $this->expectException(InvalidArgumentException::class);
1224
        $this->expectExceptionMessage('An attribute can only have an integer or a string type. bool given.');
1225
        $validator->validate([], new IteratorWithBooleanKey());
1226
    }
1227
1228
    public function testRulesWithWrongRule(): void
1229
    {
1230
        $validator = new Validator();
1231
1232
        $this->expectException(InvalidArgumentException::class);
1233
        $message = 'Rule should be either an instance of Yiisoft\Validator\RuleInterface or a callable, int given.';
1234
        $this->expectExceptionMessage($message);
1235
        $validator->validate([], [new Boolean(), 1]);
1236
    }
1237
1238
    public function testRulesAsObjectNameWithRuleAttributes(): void
1239
    {
1240
        $validator = new Validator();
1241
        $result = $validator->validate(['name' => 'Test name'], ObjectWithAttributesOnly::class);
1242
        $this->assertTrue($result->isValid());
1243
    }
1244
1245
    public function testRulesAsObjectWithRuleAttributes(): void
1246
    {
1247
        $validator = new Validator();
1248
        $result = $validator->validate(['name' => 'Test name'], new ObjectWithAttributesOnly());
1249
        $this->assertTrue($result->isValid());
1250
    }
1251
1252
    public function testDataWithPostValidationHook(): void
1253
    {
1254
        $validator = new Validator();
1255
        $this->assertFalse(ObjectWithPostValidationHook::$hookCalled);
1256
1257
        $result = $validator->validate(new ObjectWithPostValidationHook(), ['called' => new Boolean()]);
1258
        $this->assertFalse($result->isValid());
1259
        $this->assertTrue(ObjectWithPostValidationHook::$hookCalled);
1260
    }
1261
1262
    public function testSkippingRuleInPreValidate(): void
1263
    {
1264
        $data = ['agree' => false, 'viewsCount' => -1];
1265
        $rules = [
1266
            'agree' => [new Boolean(skipOnEmpty: static fn (): bool => true), new IsTrue()],
1267
            'viewsCount' => [new Number(asInteger: true, min: 0)],
1268
        ];
1269
        $validator = new Validator();
1270
1271
        $result = $validator->validate($data, $rules);
1272
        $this->assertSame(
1273
            [
1274
                'agree' => ['The value must be "1".'],
1275
                'viewsCount' => ['Value must be no less than 0.'],
1276
            ],
1277
            $result->getErrorMessagesIndexedByPath(),
1278
        );
1279
    }
1280
1281
    public function testDefaultTranslatorWithIntl(): void
1282
    {
1283
        $data = ['number' => 3];
1284
        $rules = [
1285
            'number' => new Number(
1286
                asInteger: true,
1287
                max: 2,
1288
                tooBigMessage: '{value, selectordinal, one{#-one} two{#-two} few{#-few} other{#-other}}',
1289
            ),
1290
        ];
1291
        $validator = new Validator();
1292
1293
        $result = $validator->validate($data, $rules);
1294
        $this->assertSame(['number' => ['3-few']], $result->getErrorMessagesIndexedByPath());
1295
    }
1296
1297
    public function dataSimpleForm(): array
1298
    {
1299
        return [
1300
            [
1301
                [
1302
                    'name' => [
1303
                        'Имя плохое.',
1304
                    ],
1305
                    'mail' => [
1306
                        'This value is not a valid email address.',
1307
                    ],
1308
                ],
1309
                null,
1310
            ],
1311
            [
1312
                [
1313
                    'name' => [
1314
                        'name плохое.',
1315
                    ],
1316
                    'mail' => [
1317
                        'This value is not a valid email address.',
1318
                    ],
1319
                ],
1320
                new ValidationContext(attributeTranslator: new NullAttributeTranslator()),
1321
            ],
1322
        ];
1323
    }
1324
1325
    /**
1326
     * @dataProvider dataSimpleForm
1327
     */
1328
    public function testSimpleForm(array $expectedMessages, ?ValidationContext $validationContext): void
1329
    {
1330
        $form = new SimpleForm();
1331
1332
        $result = (new Validator())->validate($form, context: $validationContext);
1333
1334
        $this->assertSame(
1335
            $expectedMessages,
1336
            $result->getErrorMessagesIndexedByPath()
1337
        );
1338
    }
1339
1340
    public function testRuleWithBuiltInHandler(): void
1341
    {
1342
        $rule = new RuleWithBuiltInHandler();
1343
1344
        $result = (new Validator())->validate(19, $rule);
1345
1346
        $this->assertSame(
1347
            ['' => ['Value must be 42.']],
1348
            $result->getErrorMessagesIndexedByPath()
1349
        );
1350
    }
1351
}
1352