Passed
Pull Request — master (#548)
by Sergei
03:01
created

php$11 ➔ testObjectWithPostValidationHook()   A

Complexity

Conditions 1

Size

Total Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

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

292
            ['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

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

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

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

821
                            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...
822
                        ),
823
                    ],
824
                ],
825
                [
826
                    new Error($stringLessThanMinMessage, [
827
                        'min' => 8,
828
                        'attribute' => 'name',
829
                        'number' => 7,
830
                    ], ['name']),
831
                    new Error($intLessThanMinMessage, [
832
                        'min' => 18,
833
                        'attribute' => 'age',
834
                        'value' => 17,
835
                    ], ['age']),
836
                ],
837
            ],
838
            'rule, skipOnEmpty, custom callback, value is not empty (null)' => [
839
                $validator,
840
                new ArrayDataSet([
841
                    'name' => 'Dmitriy',
842
                    'age' => null,
843
                ]),
844
                [
845
                    'name' => [new Length(min: 8)],
846
                    'age' => [
847
                        new Integer(
848
                            min: 18,
849
                            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

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

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

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

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

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