Passed
Pull Request — master (#464)
by Sergei
03:07
created

ValidatorTest::testNullAsDataSet()

Size

Total Lines 6
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

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

294
            ['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...
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

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

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

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

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

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

993
                    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...
994
                ),
995
                new ArrayDataSet([
996
                    'name' => 'Dmitriy',
997
                ]),
998
                $rules,
999
                [
1000
                    new Error($stringLessThanMinMessage, [
1001
                        'min' => 8,
1002
                        'attribute' => 'name',
1003
                        'number' => 7,
1004
                    ], ['name']),
1005
                    new Error($incorrectNumberMessage, [
1006
                        'attribute' => 'age',
1007
                        'type' => 'null',
1008
                    ], ['age']),
1009
                ],
1010
            ],
1011
            'validator, skipOnEmpty: custom callback, value is empty' => [
1012
                new Validator(
1013
                    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

1013
                    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...
1014
                ),
1015
                new ArrayDataSet([
1016
                    'name' => 'Dmitriy',
1017
                    'age' => 0,
1018
                ]),
1019
                $rules,
1020
                [
1021
                    new Error($stringLessThanMinMessage, [
1022
                        'min' => 8,
1023
                        'attribute' => 'name',
1024
                        'number' => 7,
1025
                    ], ['name']),
1026
                ],
1027
            ],
1028
            'validator, skipOnEmpty: custom callback, value is not empty' => [
1029
                new Validator(
1030
                    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

1030
                    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...
1031
                ),
1032
                new ArrayDataSet([
1033
                    'name' => 'Dmitriy',
1034
                    'age' => 17,
1035
                ]),
1036
                $rules,
1037
                [
1038
                    new Error($stringLessThanMinMessage, [
1039
                        'min' => 8,
1040
                        'attribute' => 'name',
1041
                        'number' => 7,
1042
                    ], ['name']),
1043
                    new Error($intLessThanMinMessage, [
1044
                        'min' => 18,
1045
                        'attribute' => 'age',
1046
                        'value' => 17,
1047
                    ], ['age']),
1048
                ],
1049
            ],
1050
            'validator, skipOnEmpty: custom callback, value is not empty (null)' => [
1051
                new Validator(
1052
                    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

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