Passed
Push — master ( 02aece...19e9a2 )
by Sergei
02:48
created

php$11 ➔ testRuleWithBuiltInHandler()   A

Complexity

Conditions 1

Size

Total Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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