Passed
Push — master ( dbf0a8...b29633 )
by
unknown
02:35
created

php$0 ➔ dataDataAndRulesCombinations()   B

Complexity

Conditions 1

Size

Total Lines 125

Duplication

Lines 0
Ratio 0 %

Importance

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

1 Method

Rating   Name   Duplication   Size   Complexity  
A ValidatorTest.php$0 ➔ getRules() 0 4 1

How to fix   Long Method   

Long Method

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

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

Commonly applied refactorings include:

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

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

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

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

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

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

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

999
                    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...
1000
                ),
1001
                new ArrayDataSet([
1002
                    'name' => 'Dmitriy',
1003
                ]),
1004
                $rules,
1005
                [
1006
                    new Error($stringLessThanMinMessage, [
1007
                        'min' => 8,
1008
                        'attribute' => 'name',
1009
                        'number' => 7,
1010
                    ], ['name']),
1011
                    new Error($incorrectNumberMessage, [
1012
                        'attribute' => 'age',
1013
                        'type' => 'null',
1014
                    ], ['age']),
1015
                ],
1016
            ],
1017
            'validator, skipOnEmpty: custom callback, value is empty' => [
1018
                new Validator(
1019
                    new SimpleRuleHandlerContainer(),
1020
                    $translator,
1021
                    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

1021
                    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...
1022
                ),
1023
                new ArrayDataSet([
1024
                    'name' => 'Dmitriy',
1025
                    'age' => 0,
1026
                ]),
1027
                $rules,
1028
                [
1029
                    new Error($stringLessThanMinMessage, [
1030
                        'min' => 8,
1031
                        'attribute' => 'name',
1032
                        'number' => 7,
1033
                    ], ['name']),
1034
                ],
1035
            ],
1036
            'validator, skipOnEmpty: custom callback, value is not empty' => [
1037
                new Validator(
1038
                    new SimpleRuleHandlerContainer(),
1039
                    $translator,
1040
                    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

1040
                    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...
1041
                ),
1042
                new ArrayDataSet([
1043
                    'name' => 'Dmitriy',
1044
                    'age' => 17,
1045
                ]),
1046
                $rules,
1047
                [
1048
                    new Error($stringLessThanMinMessage, [
1049
                        'min' => 8,
1050
                        'attribute' => 'name',
1051
                        'number' => 7,
1052
                    ], ['name']),
1053
                    new Error($intLessThanMinMessage, [
1054
                        'min' => 18,
1055
                        'attribute' => 'age',
1056
                        'value' => 17,
1057
                    ], ['age']),
1058
                ],
1059
            ],
1060
            'validator, skipOnEmpty: custom callback, value is not empty (null)' => [
1061
                new Validator(
1062
                    new SimpleRuleHandlerContainer(),
1063
                    $translator,
1064
                    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

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