Passed
Pull Request — master (#644)
by
unknown
08:56
created

ValidatorTest.php$4 ➔ requiredDataProvider()   B

Complexity

Conditions 2

Size

Total Lines 145

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 145
cc 2
rs 8

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\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\Label;
21
use Yiisoft\Validator\Result;
22
use Yiisoft\Validator\Rule\AtLeast;
23
use Yiisoft\Validator\Rule\BooleanValue;
24
use Yiisoft\Validator\Rule\Compare;
25
use Yiisoft\Validator\Rule\In;
26
use Yiisoft\Validator\Rule\Integer;
27
use Yiisoft\Validator\Rule\Length;
28
use Yiisoft\Validator\Rule\Number;
29
use Yiisoft\Validator\Rule\Required;
30
use Yiisoft\Validator\Rule\TrueValue;
31
use Yiisoft\Validator\RuleInterface;
32
use Yiisoft\Validator\RulesProviderInterface;
33
use Yiisoft\Validator\Tests\Rule\RuleWithBuiltInHandler;
34
use Yiisoft\Validator\Tests\Support\Data\DataSetWithPostValidationHook;
35
use Yiisoft\Validator\Tests\Support\Data\EachNestedObjects\Foo;
36
use Yiisoft\Validator\Tests\Support\Data\IteratorWithBooleanKey;
37
use Yiisoft\Validator\Tests\Support\Data\ObjectWithAttributesOnly;
38
use Yiisoft\Validator\Tests\Support\Data\ObjectWithDataSet;
39
use Yiisoft\Validator\Tests\Support\Data\ObjectWithDataSetAndRulesProvider;
40
use Yiisoft\Validator\Tests\Support\Data\ObjectWithDifferentPropertyVisibility;
41
use Yiisoft\Validator\Tests\Support\Data\ObjectWithLabelsProvider;
42
use Yiisoft\Validator\Tests\Support\Data\ObjectWithPostValidationHook;
43
use Yiisoft\Validator\Tests\Support\Data\ObjectWithRulesProvider;
44
use Yiisoft\Validator\Tests\Support\Data\SimpleDto;
45
use Yiisoft\Validator\Tests\Support\Data\SimpleForm;
46
use Yiisoft\Validator\Tests\Support\Rule\NotNullRule\NotNull;
47
use Yiisoft\Validator\Tests\Support\Rule\StubRule\StubDumpedRule;
48
use Yiisoft\Validator\ValidationContext;
49
use Yiisoft\Validator\Validator;
50
use Yiisoft\Validator\ValidatorInterface;
51
52
class ValidatorTest extends TestCase
53
{
54
    public function testBase(): void
55
    {
56
        $validator = new Validator();
57
58
        $result = $validator->validate(new ObjectWithAttributesOnly());
59
60
        $this->assertFalse($result->isValid());
61
        $this->assertSame(
62
            ['name' => ['The value must contain at least 5 characters.']],
63
            $result->getErrorMessagesIndexedByPath()
64
        );
65
    }
66
67
    public function testWithDefaultSkipOnEmptyCondition(): void
68
    {
69
        $data = '';
70
        $rule = new Length(1);
71
        $validator = new Validator();
72
73
        $result = $validator->validate($data, $rule);
74
        $this->assertFalse($result->isValid());
75
76
        $newValidator = $validator->withDefaultSkipOnEmptyCondition(true);
77
        $this->assertNotSame($validator, $newValidator);
78
79
        $result = $newValidator->validate($data, $rule);
80
        $this->assertTrue($result->isValid());
81
    }
82
83
    public function dataDataAndRulesCombinations(): array
84
    {
85
        return [
86
            'pure-object-and-array-of-rules' => [
87
                [
88
                    'number' => ['The value must be no less than 77.'],
89
                ],
90
                new ObjectWithDifferentPropertyVisibility(),
91
                [
92
                    'age' => new Number(max: 100),
93
                    'number' => new Number(min: 77),
94
                ],
95
            ],
96
            'pure-object-and-no-rules' => [
97
                [
98
                    'name' => ['The value cannot be blank.'],
99
                    'age' => ['The value must be no less than 21.'],
100
                ],
101
                new ObjectWithDifferentPropertyVisibility(),
102
                null,
103
            ],
104
            'dataset-object-and-array-of-rules' => [
105
                [
106
                    'key1' => ['The value must be no less than 21.'],
107
                ],
108
                new ObjectWithDataSet(),
109
                [
110
                    'key1' => new Number(min: 21),
111
                ],
112
            ],
113
            'dataset-object-and-no-rules' => [
114
                [],
115
                new ObjectWithDataSet(),
116
                null,
117
            ],
118
            'rules-provider-object-and-array-of-rules' => [
119
                [
120
                    'number' => ['The value must be no greater than 7.'],
121
                ],
122
                new ObjectWithRulesProvider(),
123
                [
124
                    'age' => new Number(max: 100),
125
                    'number' => new Number(max: 7),
126
                ],
127
            ],
128
            'rules-provider-object-and-no-rules' => [
129
                [
130
                    'age' => ['The value must be equal to "25".'],
131
                ],
132
                new ObjectWithRulesProvider(),
133
                null,
134
            ],
135
            'rules-provider-and-dataset-object-and-array-of-rules' => [
136
                [
137
                    'key2' => ['The value must be no greater than 7.'],
138
                ],
139
                new ObjectWithDataSetAndRulesProvider(),
140
                [
141
                    'key2' => new Number(max: 7),
142
                ],
143
            ],
144
            'rules-provider-and-dataset-object-and-no-rules' => [
145
                [
146
                    'key2' => ['The value must be equal to "99".'],
147
                ],
148
                new ObjectWithDataSetAndRulesProvider(),
149
                null,
150
            ],
151
            'array-and-array-of-rules' => [
152
                [
153
                    'key2' => ['The value must be no greater than 7.'],
154
                ],
155
                ['key1' => 15, 'key2' => 99],
156
                [
157
                    'key1' => new Number(max: 100),
158
                    'key2' => new Number(max: 7),
159
                ],
160
            ],
161
            'array-and-no-rules' => [
162
                [],
163
                ['key1' => 15, 'key2' => 99],
164
                null,
165
            ],
166
            'scalar-and-array-of-rules' => [
167
                [
168
                    '' => ['The value must be no greater than 7.'],
169
                ],
170
                42,
171
                [
172
                    new Number(max: 7),
173
                ],
174
            ],
175
            'scalar-and-no-rules' => [
176
                [],
177
                42,
178
                null,
179
            ],
180
            'array-and-rules-provider' => [
181
                [
182
                    'age' => ['The value must be no less than 18.'],
183
                ],
184
                [
185
                    'age' => 17,
186
                ],
187
                new class () implements RulesProviderInterface {
188
                    public function getRules(): iterable
189
                    {
190
                        return [
191
                            'age' => [new Number(min: 18)],
192
                        ];
193
                    }
194
                },
195
            ],
196
            'array-and-object' => [
197
                [
198
                    'name' => ['The value not passed.'],
199
                    'bars' => ['The value must be array or iterable.'],
200
                ],
201
                [],
202
                new Foo(),
203
            ],
204
            'array-and-callable' => [
205
                ['' => ['test message']],
206
                [],
207
                static fn (): Result => (new Result())->addError('test message'),
208
            ],
209
        ];
210
    }
211
212
    /**
213
     * @dataProvider dataDataAndRulesCombinations
214
     */
215
    public function testDataAndRulesCombinations(
216
        array $expectedErrorMessages,
217
        mixed $data,
218
        iterable|object|callable|null $rules,
219
    ): void {
220
        $validator = new Validator();
221
        $result = $validator->validate($data, $rules);
222
        $this->assertSame($expectedErrorMessages, $result->getErrorMessagesIndexedByAttribute());
223
    }
224
225
    public function dataWithEmptyArrayOfRules(): array
226
    {
227
        return [
228
            'pure-object-and-no-rules' => [new ObjectWithDifferentPropertyVisibility()],
229
            'dataset-object-and-no-rules' => [new ObjectWithDataSet()],
230
            'rules-provider-object' => [new ObjectWithRulesProvider()],
231
            'rules-provider-and-dataset-object' => [new ObjectWithDataSetAndRulesProvider()],
232
            'array' => [['key1' => 15, 'key2' => 99]],
233
            'scalar' => [42],
234
        ];
235
    }
236
237
    /**
238
     * @dataProvider dataWithEmptyArrayOfRules
239
     */
240
    public function testWithEmptyArrayOfRules(mixed $data): void
241
    {
242
        $validator = new Validator();
243
        $result = $validator->validate($data, []);
244
245
        $this->assertTrue($result->isValid());
246
    }
247
248
    public function testAddingRulesViaConstructor(): void
249
    {
250
        $dataObject = new ArrayDataSet(['bool' => true, 'int' => 41]);
251
        $validator = new Validator();
252
        $result = $validator->validate($dataObject, [
253
            'bool' => [new BooleanValue()],
254
            'int' => [
255
                new Integer(),
256
                new Integer(min: 44),
257
                static function (mixed $value): Result {
258
                    $result = new Result();
259
                    if ($value !== 42) {
260
                        $result->addError('The value should be 42!', ['int']);
261
                    }
262
263
                    return $result;
264
                },
265
            ],
266
        ]);
267
268
        $this->assertTrue($result->isAttributeValid('bool'));
269
        $this->assertFalse($result->isAttributeValid('int'));
270
    }
271
272
    public function diverseTypesDataProvider(): array
273
    {
274
        $class = new stdClass();
275
        $class->property = true;
276
277
        return [
278
            'object' => [new ObjectDataSet($class, useCache: false)],
279
            'true' => [true],
280
            'non-empty-string' => ['true'],
281
            'integer' => [12345],
282
            'float' => [12.345],
283
            'false' => [false],
284
        ];
285
    }
286
287
    /**
288
     * @dataProvider diverseTypesDataProvider
289
     */
290
    public function testDiverseTypes($dataSet): void
291
    {
292
        $validator = new Validator();
293
        $result = $validator->validate($dataSet, [new Required()]);
294
295
        $this->assertTrue($result->isValid());
296
    }
297
298
    public function testNullAsDataSet(): void
299
    {
300
        $validator = new Validator();
301
        $result = $validator->validate(null, ['property' => [new Compare()]]);
302
303
        $this->assertTrue($result->isValid());
304
    }
305
306
    public function testPreValidation(): void
307
    {
308
        $validator = new Validator();
309
        $result = $validator->validate(
310
            new ArrayDataSet(['property' => '']),
311
            ['property' => [new Required(when: static fn (mixed $value, ?ValidationContext $context): bool => false)]],
312
        );
313
314
        $this->assertTrue($result->isValid());
315
    }
316
317
    public function testRuleHandlerWithoutImplement(): void
318
    {
319
        $ruleHandler = new class () {
320
        };
321
        $validator = new Validator();
322
323
        $this->expectException(RuleHandlerInterfaceNotImplementedException::class);
324
        $validator->validate(new ArrayDataSet(['property' => '']), [
325
            'property' => [
326
                new class ($ruleHandler) implements RuleInterface {
327
                    public function __construct(private $ruleHandler)
328
                    {
329
                    }
330
331
                    public function getHandler(): string
332
                    {
333
                        return $this->ruleHandler::class;
334
                    }
335
                },
336
            ],
337
        ]);
338
    }
339
340
    public function testRuleWithoutHandler(): void
341
    {
342
        $this->expectException(RuleHandlerNotFoundException::class);
343
344
        $validator = new Validator();
345
        $validator->validate(new ArrayDataSet(['property' => '']), [
346
            'property' => [
347
                new class () implements RuleInterface {
348
                    public function getHandler(): string
349
                    {
350
                        return 'NonExistClass';
351
                    }
352
                },
353
            ],
354
        ]);
355
    }
356
357
    public function requiredDataProvider(): array
358
    {
359
        $strictRules = [
360
            'orderBy' => [new Required()],
361
            'sort' => [
362
                new In(
363
                    ['asc', 'desc'],
364
                    skipOnEmpty: static fn (mixed $value, bool $isAttributeMissing): bool => $isAttributeMissing
365
                ),
366
            ],
367
        ];
368
        $notStrictRules = [
369
            'orderBy' => [new Required()],
370
            'sort' => [
371
                new In(
372
                    ['asc', 'desc'],
373
                    skipOnEmpty: static fn (
374
                        mixed $value,
375
                        bool $isAttributeMissing
376
                    ): bool => $isAttributeMissing || $value === ''
377
                ),
378
            ],
379
        ];
380
381
        return [
382
            [
383
                ['merchantId' => [new Required(), new Integer()]],
384
                new ArrayDataSet(['merchantId' => null]),
385
                [
386
                    new Error(
387
                        'The value cannot be blank.',
388
                        ['attribute' => 'merchantId', 'label' => 'The value'],
389
                        ['merchantId']
390
                    ),
391
                    new Error(
392
                        'The allowed types are integer, float and string.',
393
                        ['attribute' => 'merchantId', 'type' => 'null', 'label' => 'The value'],
394
                        ['merchantId']
395
                    ),
396
                ],
397
            ],
398
            [
399
                ['merchantId' => [new Required(), new Integer(skipOnError: true)]],
400
                new ArrayDataSet(['merchantId' => null]),
401
                [new Error('The value cannot be blank.', [ 'attribute' => 'merchantId', 'label' => 'The value'], ['merchantId'])],
402
            ],
403
            [
404
                ['merchantId' => [new Required(), new Integer(skipOnError: true)]],
405
                new ArrayDataSet(['merchantIdd' => 1]),
406
                [new Error('The value not passed.', ['attribute' => 'merchantId', 'label' => 'The value'], ['merchantId'])],
407
            ],
408
409
            [
410
                $strictRules,
411
                new ArrayDataSet(['orderBy' => 'name', 'sort' => 'asc']),
412
                [],
413
            ],
414
            [
415
                $notStrictRules,
416
                new ArrayDataSet(['orderBy' => 'name', 'sort' => 'asc']),
417
                [],
418
            ],
419
420
            [
421
                $strictRules,
422
                new ArrayDataSet(['orderBy' => 'name', 'sort' => 'desc']),
423
                [],
424
            ],
425
            [
426
                $notStrictRules,
427
                new ArrayDataSet(['orderBy' => 'name', 'sort' => 'desc']),
428
                [],
429
            ],
430
431
            [
432
                $strictRules,
433
                new ArrayDataSet(['orderBy' => 'name', 'sort' => 'up']),
434
                [new Error('The value is not in the list of acceptable values.', ['attribute' => 'sort', 'label' => 'The value'], ['sort'])],
435
            ],
436
            [
437
                $notStrictRules,
438
                new ArrayDataSet(['orderBy' => 'name', 'sort' => 'up']),
439
                [new Error('The value is not in the list of acceptable values.', ['attribute' => 'sort', 'label' => 'The value'], ['sort'])],
440
            ],
441
442
            [
443
                $strictRules,
444
                new ArrayDataSet(['orderBy' => 'name', 'sort' => '']),
445
                [new Error('The value is not in the list of acceptable values.', ['attribute' => 'sort', 'label' => 'The value'], ['sort'])],
446
            ],
447
            [
448
                $notStrictRules,
449
                new ArrayDataSet(['orderBy' => 'name', 'sort' => '']),
450
                [],
451
            ],
452
453
            [
454
                $strictRules,
455
                new ArrayDataSet(['orderBy' => 'name']),
456
                [],
457
            ],
458
            [
459
                $notStrictRules,
460
                new ArrayDataSet(['orderBy' => 'name']),
461
                [],
462
            ],
463
464
            [
465
                $strictRules,
466
                new ArrayDataSet(['orderBy' => '']),
467
                [new Error('The value cannot be blank.', ['attribute' => 'orderBy', 'label' => 'The value'], ['orderBy'])],
468
            ],
469
            [
470
                $notStrictRules,
471
                new ArrayDataSet(['orderBy' => '']),
472
                [new Error('The value cannot be blank.', ['attribute' => 'orderBy', 'label' => 'The value'], ['orderBy'])],
473
            ],
474
475
            [
476
                $strictRules,
477
                new ArrayDataSet([]),
478
                [new Error('The value not passed.', ['attribute' => 'orderBy', 'label' => 'The value'], ['orderBy'])],
479
            ],
480
            [
481
                $notStrictRules,
482
                new ArrayDataSet([]),
483
                [new Error('The value not passed.', ['attribute' => 'orderBy', 'label' => 'The value'], ['orderBy'])],
484
            ],
485
            [
486
                [
487
                    'name' => [new Required(), new Length(min: 3, skipOnError: true)],
488
                    'description' => [new Required(), new Length(min: 5, skipOnError: true)],
489
                ],
490
                new ObjectDataSet(
491
                    new class () {
492
                        private string $title = '';
493
                        private string $description = 'abc123';
494
                    }
495
                ),
496
                [new Error('The value not passed.', ['attribute' => 'name', 'label' => 'The value'], ['name'])],
497
            ],
498
            [
499
                null,
500
                new ObjectDataSet(new ObjectWithDataSet()),
501
                [],
502
            ],
503
        ];
504
    }
505
506
    /**
507
     * @link https://github.com/yiisoft/validator/issues/173
508
     * @link https://github.com/yiisoft/validator/issues/289
509
     *
510
     * @dataProvider requiredDataProvider
511
     */
512
    public function testRequired(array|null $rules, DataSetInterface $dataSet, array $expectedErrors): void
513
    {
514
        $validator = new Validator();
515
        $result = $validator->validate($dataSet, $rules);
516
        $this->assertEquals($expectedErrors, $result->getErrors());
517
    }
518
519
    public function skipOnEmptyDataProvider(): array
520
    {
521
        $validator = new Validator();
522
        $rules = [
523
            'name' => [new Length(min: 8)],
524
            'age' => [new Integer(min: 18)],
525
        ];
526
        $stringLessThanMinMessage = 'The value must contain at least 8 characters.';
527
        $incorrectNumberMessage = 'The allowed types are integer, float and string.';
528
        $intMessage = 'The value must be an integer.';
529
        $intLessThanMinMessage = 'The value must be no less than 18.';
530
531
        return [
532
            'rule / validator, skipOnEmpty: false, value not passed' => [
533
                $validator,
534
                new ArrayDataSet([
535
                    'name' => 'Dmitriy',
536
                ]),
537
                $rules,
538
                [
539
                    new Error($stringLessThanMinMessage, [
540
                        'min' => 8,
541
                        'attribute' => 'name',
542
                        'number' => 7,
543
                        'label' => 'The value',
544
                    ], ['name']),
545
                    new Error($incorrectNumberMessage, [
546
                        'attribute' => 'age',
547
                        'type' => 'null',
548
                        'label' => 'The value',
549
                    ], ['age']),
550
                ],
551
            ],
552
            'rule / validator, skipOnEmpty: false, value is empty' => [
553
                $validator,
554
                new ArrayDataSet([
555
                    'name' => 'Dmitriy',
556
                    'age' => null,
557
                ]),
558
                $rules,
559
                [
560
                    new Error($stringLessThanMinMessage, [
561
                        'min' => 8,
562
                        'attribute' => 'name',
563
                        'number' => 7,
564
                        'label' => 'The value',
565
                    ], ['name']),
566
                    new Error($incorrectNumberMessage, [
567
                        'attribute' => 'age',
568
                        'type' => 'null',
569
                        'label' => 'The value',
570
                    ], ['age']),
571
                ],
572
            ],
573
            'rule / validator, skipOnEmpty: false, value is not empty' => [
574
                $validator,
575
                new ArrayDataSet([
576
                    'name' => 'Dmitriy',
577
                    'age' => 17,
578
                ]),
579
                $rules,
580
                [
581
                    new Error($stringLessThanMinMessage, [
582
                        'min' => 8,
583
                        'attribute' => 'name',
584
                        'number' => 7,
585
                        'label' => 'The value',
586
                    ], ['name']),
587
                    new Error($intLessThanMinMessage, [
588
                        'min' => 18,
589
                        'attribute' => 'age',
590
                        'value' => 17,
591
                        'label' => 'The value',
592
                    ], ['age']),
593
                ],
594
            ],
595
596
            'rule, skipOnEmpty: true, value not passed' => [
597
                $validator,
598
                new ArrayDataSet([
599
                    'name' => 'Dmitriy',
600
                ]),
601
                [
602
                    'name' => [new Length(min: 8)],
603
                    'age' => [new Integer(min: 18, skipOnEmpty: true)],
604
                ],
605
                [
606
                    new Error($stringLessThanMinMessage, [
607
                        'min' => 8,
608
                        'attribute' => 'name',
609
                        'number' => 7,
610
                        'label' => 'The value',
611
                    ], ['name']),
612
                ],
613
            ],
614
            'rule, skipOnEmpty: true, value is empty (null)' => [
615
                $validator,
616
                new ArrayDataSet([
617
                    'name' => 'Dmitriy',
618
                    'age' => null,
619
                ]),
620
                [
621
                    'name' => [new Length(min: 8)],
622
                    'age' => [new Integer(min: 18, skipOnEmpty: true)],
623
                ],
624
                [
625
                    new Error($stringLessThanMinMessage, [
626
                        'min' => 8,
627
                        'attribute' => 'name',
628
                        'number' => 7,
629
                        'label' => 'The value',
630
                    ], ['name']),
631
                ],
632
            ],
633
            'rule, skipOnEmpty: true, value is empty (empty string after trimming), trimString is false' => [
634
                $validator,
635
                new ArrayDataSet([
636
                    'name' => ' ',
637
                    'age' => 17,
638
                ]),
639
                [
640
                    'name' => [new Length(min: 8, skipOnEmpty: true)],
641
                    'age' => [new Integer(min: 18)],
642
                ],
643
                [
644
                    new Error($stringLessThanMinMessage, [
645
                        'min' => 8,
646
                        'attribute' => 'name',
647
                        'number' => 1,
648
                        'label' => 'The value',
649
                    ], ['name']),
650
                    new Error($intLessThanMinMessage, [
651
                        'min' => 18,
652
                        'attribute' => 'age',
653
                        'value' => 17,
654
                        'label' => 'The value',
655
                    ], ['age']),
656
                ],
657
            ],
658
            'rule, skipOnEmpty: SkipOnEmpty, value is empty (empty string after trimming), trimString is true' => [
659
                $validator,
660
                new ArrayDataSet([
661
                    'name' => ' ',
662
                    'age' => 17,
663
                ]),
664
                [
665
                    'name' => [new Length(min: 8, skipOnEmpty: new WhenEmpty(trimString: true))],
666
                    'age' => [new Integer(min: 18)],
667
                ],
668
                [
669
                    new Error($intLessThanMinMessage, [
670
                        'min' => 18,
671
                        'attribute' => 'age',
672
                        'value' => 17,
673
                        'label' => 'The value',
674
                    ], ['age']),
675
                ],
676
            ],
677
            'rule, skipOnEmpty: true, value is not empty' => [
678
                $validator,
679
                new ArrayDataSet([
680
                    'name' => 'Dmitriy',
681
                    'age' => 17,
682
                ]),
683
                [
684
                    'name' => [new Length(min: 8)],
685
                    'age' => [new Integer(min: 18, skipOnEmpty: true)],
686
                ],
687
                [
688
                    new Error($stringLessThanMinMessage, [
689
                        'min' => 8,
690
                        'attribute' => 'name',
691
                        'number' => 7,
692
                        'label' => 'The value',
693
                    ], ['name']),
694
                    new Error($intLessThanMinMessage, [
695
                        'min' => 18,
696
                        'attribute' => 'age',
697
                        'value' => 17,
698
                        'label' => 'The value',
699
                    ], ['age']),
700
                ],
701
            ],
702
703
            'rule, skipOnEmpty: SkipOnNull, value not passed' => [
704
                $validator,
705
                new ArrayDataSet([
706
                    'name' => 'Dmitriy',
707
                ]),
708
                [
709
                    'name' => [new Length(min: 8)],
710
                    'age' => [new Integer(min: 18, skipOnEmpty: new WhenNull())],
711
                ],
712
                [
713
                    new Error($stringLessThanMinMessage, [
714
                        'min' => 8,
715
                        'attribute' => 'name',
716
                        'number' => 7,
717
                        'label' => 'The value',
718
                    ], ['name']),
719
                ],
720
            ],
721
            'rule, skipOnEmpty: SkipOnNull, value is empty' => [
722
                $validator,
723
                new ArrayDataSet([
724
                    'name' => 'Dmitriy',
725
                    'age' => null,
726
                ]),
727
                [
728
                    'name' => [new Length(min: 8)],
729
                    'age' => [new Integer(min: 18, skipOnEmpty: new WhenNull())],
730
                ],
731
                [
732
                    new Error($stringLessThanMinMessage, [
733
                        'min' => 8,
734
                        'attribute' => 'name',
735
                        'number' => 7,
736
                        'label' => 'The value',
737
                    ], ['name']),
738
                ],
739
            ],
740
            'rule, skipOnEmpty: SkipOnNull, value is not empty' => [
741
                $validator,
742
                new ArrayDataSet([
743
                    'name' => 'Dmitriy',
744
                    'age' => 17,
745
                ]),
746
                [
747
                    'name' => [new Length(min: 8)],
748
                    'age' => [new Integer(min: 18, skipOnEmpty: new WhenNull())],
749
                ],
750
                [
751
                    new Error($stringLessThanMinMessage, [
752
                        'min' => 8,
753
                        'attribute' => 'name',
754
                        'number' => 7,
755
                        'label' => 'The value',
756
                    ], ['name']),
757
                    new Error($intLessThanMinMessage, [
758
                        'min' => 18,
759
                        'attribute' => 'age',
760
                        'value' => 17,
761
                        'label' => 'The value',
762
                    ], ['age']),
763
                ],
764
            ],
765
            'rule, skipOnEmpty: SkipOnNull, value is not empty (empty string)' => [
766
                $validator,
767
                new ArrayDataSet([
768
                    'name' => 'Dmitriy',
769
                    'age' => '',
770
                ]),
771
                [
772
                    'name' => [new Length(min: 8)],
773
                    'age' => [new Integer(min: 18, skipOnEmpty: new WhenNull())],
774
                ],
775
                [
776
                    new Error($stringLessThanMinMessage, [
777
                        'min' => 8,
778
                        'attribute' => 'name',
779
                        'number' => 7,
780
                        'label' => 'The value',
781
                    ], ['name']),
782
                    new Error($intMessage, [
783
                        'attribute' => 'age',
784
                        'value' => '',
785
                        'label' => 'The value',
786
                    ], ['age']),
787
                ],
788
            ],
789
790
            'rule, skipOnEmpty: custom callback, value not passed' => [
791
                $validator,
792
                new ArrayDataSet([
793
                    'name' => 'Dmitriy',
794
                ]),
795
                [
796
                    'name' => [new Length(min: 8)],
797
                    'age' => [
798
                        new Integer(
799
                            min: 18,
800
                            skipOnEmpty: static fn (mixed $value, bool $isAttributeMissing): bool => $value === 0
801
                        ),
802
                    ],
803
                ],
804
                [
805
                    new Error($stringLessThanMinMessage, [
806
                        'min' => 8,
807
                        'attribute' => 'name',
808
                        'number' => 7,
809
                        'label' => 'The value',
810
                    ], ['name']),
811
                    new Error($incorrectNumberMessage, [
812
                        'attribute' => 'age',
813
                        'type' => 'null',
814
                        'label' => 'The value',
815
                    ], ['age']),
816
                ],
817
            ],
818
            'rule, skipOnEmpty: custom callback, value is empty' => [
819
                $validator,
820
                new ArrayDataSet([
821
                    'name' => 'Dmitriy',
822
                    'age' => 0,
823
                ]),
824
                [
825
                    'name' => [new Length(min: 8)],
826
                    'age' => [
827
                        new Integer(
828
                            min: 18,
829
                            skipOnEmpty: static fn (mixed $value, bool $isAttributeMissing): bool => $value === 0
830
                        ),
831
                    ],
832
                ],
833
                [
834
                    new Error($stringLessThanMinMessage, [
835
                        'min' => 8,
836
                        'attribute' => 'name',
837
                        'number' => 7,
838
                        'label' => 'The value',
839
                    ], ['name']),
840
                ],
841
            ],
842
            'rule, skipOnEmpty, custom callback, value is not empty' => [
843
                $validator,
844
                new ArrayDataSet([
845
                    'name' => 'Dmitriy',
846
                    'age' => 17,
847
                ]),
848
                [
849
                    'name' => [new Length(min: 8)],
850
                    'age' => [
851
                        new Integer(
852
                            min: 18,
853
                            skipOnEmpty: static fn (mixed $value, bool $isAttributeMissing): bool => $value === 0
854
                        ),
855
                    ],
856
                ],
857
                [
858
                    new Error($stringLessThanMinMessage, [
859
                        'min' => 8,
860
                        'attribute' => 'name',
861
                        'number' => 7,
862
                        'label' => 'The value',
863
                    ], ['name']),
864
                    new Error($intLessThanMinMessage, [
865
                        'min' => 18,
866
                        'attribute' => 'age',
867
                        'value' => 17,
868
                        'label' => 'The value',
869
                    ], ['age']),
870
                ],
871
            ],
872
            'rule, skipOnEmpty, custom callback, value is not empty (null)' => [
873
                $validator,
874
                new ArrayDataSet([
875
                    'name' => 'Dmitriy',
876
                    'age' => null,
877
                ]),
878
                [
879
                    'name' => [new Length(min: 8)],
880
                    'age' => [
881
                        new Integer(
882
                            min: 18,
883
                            skipOnEmpty: static fn (mixed $value, bool $isAttributeMissing): bool => $value === 0
884
                        ),
885
                    ],
886
                ],
887
                [
888
                    new Error($stringLessThanMinMessage, [
889
                        'min' => 8,
890
                        'attribute' => 'name',
891
                        'number' => 7,
892
                        'label' => 'The value',
893
                    ], ['name']),
894
                    new Error($incorrectNumberMessage, [
895
                        'attribute' => 'age',
896
                        'type' => 'null',
897
                        'label' => 'The value',
898
                    ], ['age']),
899
                ],
900
            ],
901
902
            'validator, skipOnEmpty: true, value not passed' => [
903
                new Validator(defaultSkipOnEmpty: true),
904
                new ArrayDataSet([
905
                    'name' => 'Dmitriy',
906
                ]),
907
                $rules,
908
                [
909
                    new Error($stringLessThanMinMessage, [
910
                        'min' => 8,
911
                        'attribute' => 'name',
912
                        'number' => 7,
913
                        'label' => 'The value',
914
                    ], ['name']),
915
                ],
916
            ],
917
            'validator, skipOnEmpty: true, value is empty' => [
918
                new Validator(defaultSkipOnEmpty: true),
919
                new ArrayDataSet([
920
                    'name' => 'Dmitriy',
921
                    'age' => null,
922
                ]),
923
                $rules,
924
                [
925
                    new Error($stringLessThanMinMessage, [
926
                        'min' => 8,
927
                        'attribute' => 'name',
928
                        'number' => 7,
929
                        'label' => 'The value',
930
                    ], ['name']),
931
                ],
932
            ],
933
            'validator, skipOnEmpty: true, value is not empty' => [
934
                new Validator(defaultSkipOnEmpty: true),
935
                new ArrayDataSet([
936
                    'name' => 'Dmitriy',
937
                    'age' => 17,
938
                ]),
939
                $rules,
940
                [
941
                    new Error($stringLessThanMinMessage, [
942
                        'min' => 8,
943
                        'attribute' => 'name',
944
                        'number' => 7,
945
                        'label' => 'The value',
946
                    ], ['name']),
947
                    new Error($intLessThanMinMessage, [
948
                        'min' => 18,
949
                        'attribute' => 'age',
950
                        'value' => 17,
951
                        'label' => 'The value',
952
                    ], ['age']),
953
                ],
954
            ],
955
956
            'validator, skipOnEmpty: SkipOnNull, value not passed' => [
957
                new Validator(defaultSkipOnEmpty: new WhenNull()),
958
                new ArrayDataSet([
959
                    'name' => 'Dmitriy',
960
                ]),
961
                $rules,
962
                [
963
                    new Error($stringLessThanMinMessage, [
964
                        'min' => 8,
965
                        'attribute' => 'name',
966
                        'number' => 7,
967
                        'label' => 'The value',
968
                    ], ['name']),
969
                ],
970
            ],
971
            'validator, skipOnEmpty: SkipOnNull, value is empty' => [
972
                new Validator(defaultSkipOnEmpty: new WhenNull()),
973
                new ArrayDataSet([
974
                    'name' => 'Dmitriy',
975
                    'age' => null,
976
                ]),
977
                $rules,
978
                [
979
                    new Error($stringLessThanMinMessage, [
980
                        'min' => 8,
981
                        'attribute' => 'name',
982
                        'number' => 7,
983
                        'label' => 'The value',
984
                    ], ['name']),
985
                ],
986
            ],
987
            'validator, skipOnEmpty: SkipOnNull, value is not empty' => [
988
                new Validator(defaultSkipOnEmpty: new WhenNull()),
989
                new ArrayDataSet([
990
                    'name' => 'Dmitriy',
991
                    'age' => 17,
992
                ]),
993
                $rules,
994
                [
995
                    new Error($stringLessThanMinMessage, [
996
                        'min' => 8,
997
                        'attribute' => 'name',
998
                        'number' => 7,
999
                        'label' => 'The value',
1000
                    ], ['name']),
1001
                    new Error($intLessThanMinMessage, [
1002
                        'min' => 18,
1003
                        'attribute' => 'age',
1004
                        'value' => 17,
1005
                        'label' => 'The value',
1006
                    ], ['age']),
1007
                ],
1008
            ],
1009
            'validator, skipOnEmpty: SkipOnNull, value is not empty (empty string)' => [
1010
                new Validator(defaultSkipOnEmpty: new WhenNull()),
1011
                new ArrayDataSet([
1012
                    'name' => 'Dmitriy',
1013
                    'age' => '',
1014
                ]),
1015
                $rules,
1016
                [
1017
                    new Error($stringLessThanMinMessage, [
1018
                        'min' => 8,
1019
                        'attribute' => 'name',
1020
                        'number' => 7,
1021
                        'label' => 'The value',
1022
                    ], ['name']),
1023
                    new Error($intMessage, [
1024
                        'attribute' => 'age',
1025
                        'value' => '',
1026
                        'label' => 'The value',
1027
                    ], ['age']),
1028
                ],
1029
            ],
1030
1031
            'validator, skipOnEmpty: custom callback, value not passed' => [
1032
                new Validator(
1033
                    defaultSkipOnEmpty: static fn (mixed $value, bool $isAttributeMissing): bool => $value === 0
1034
                ),
1035
                new ArrayDataSet([
1036
                    'name' => 'Dmitriy',
1037
                ]),
1038
                $rules,
1039
                [
1040
                    new Error($stringLessThanMinMessage, [
1041
                        'min' => 8,
1042
                        'attribute' => 'name',
1043
                        'number' => 7,
1044
                        'label' => 'The value',
1045
                    ], ['name']),
1046
                    new Error($incorrectNumberMessage, [
1047
                        'attribute' => 'age',
1048
                        'type' => 'null',
1049
                        'label' => 'The value',
1050
                    ], ['age']),
1051
                ],
1052
            ],
1053
            'validator, skipOnEmpty: custom callback, value is empty' => [
1054
                new Validator(
1055
                    defaultSkipOnEmpty: static fn (mixed $value, bool $isAttributeMissing): bool => $value === 0
1056
                ),
1057
                new ArrayDataSet([
1058
                    'name' => 'Dmitriy',
1059
                    'age' => 0,
1060
                ]),
1061
                $rules,
1062
                [
1063
                    new Error($stringLessThanMinMessage, [
1064
                        'min' => 8,
1065
                        'attribute' => 'name',
1066
                        'number' => 7,
1067
                        'label' => 'The value',
1068
                    ], ['name']),
1069
                ],
1070
            ],
1071
            'validator, skipOnEmpty: custom callback, value is not empty' => [
1072
                new Validator(
1073
                    defaultSkipOnEmpty: static fn (mixed $value, bool $isAttributeMissing): bool => $value === 0
1074
                ),
1075
                new ArrayDataSet([
1076
                    'name' => 'Dmitriy',
1077
                    'age' => 17,
1078
                ]),
1079
                $rules,
1080
                [
1081
                    new Error($stringLessThanMinMessage, [
1082
                        'min' => 8,
1083
                        'attribute' => 'name',
1084
                        'number' => 7,
1085
                        'label' => 'The value',
1086
                    ], ['name']),
1087
                    new Error($intLessThanMinMessage, [
1088
                        'min' => 18,
1089
                        'attribute' => 'age',
1090
                        'value' => 17,
1091
                        'label' => 'The value',
1092
                    ], ['age']),
1093
                ],
1094
            ],
1095
            'validator, skipOnEmpty: custom callback, value is not empty (null)' => [
1096
                new Validator(
1097
                    defaultSkipOnEmpty: static fn (mixed $value, bool $isAttributeMissing): bool => $value === 0
1098
                ),
1099
                new ArrayDataSet([
1100
                    'name' => 'Dmitriy',
1101
                    'age' => null,
1102
                ]),
1103
                $rules,
1104
                [
1105
                    new Error($stringLessThanMinMessage, [
1106
                        'min' => 8,
1107
                        'attribute' => 'name',
1108
                        'number' => 7,
1109
                        'label' => 'The value',
1110
                    ], ['name']),
1111
                    new Error($incorrectNumberMessage, [
1112
                        'attribute' => 'age',
1113
                        'type' => 'null',
1114
                        'label' => 'The value',
1115
                    ], ['age']),
1116
                ],
1117
            ],
1118
        ];
1119
    }
1120
1121
    /**
1122
     * @param StubDumpedRule[] $rules
1123
     * @param Error[] $expectedErrors
1124
     *
1125
     * @dataProvider skipOnEmptyDataProvider
1126
     */
1127
    public function testSkipOnEmpty(Validator $validator, ArrayDataSet $data, array $rules, array $expectedErrors): void
1128
    {
1129
        $result = $validator->validate($data, $rules);
1130
        $this->assertEquals($expectedErrors, $result->getErrors());
1131
    }
1132
1133
    public function initSkipOnEmptyDataProvider(): array
1134
    {
1135
        return [
1136
            'null' => [
1137
                null,
1138
                new class () {
1139
                    #[Number]
1140
                    public ?string $name = null;
1141
                },
1142
                false,
1143
            ],
1144
            'false' => [
1145
                false,
1146
                new class () {
1147
                    #[Number]
1148
                    public ?string $name = null;
1149
                },
1150
                false,
1151
            ],
1152
            'true' => [
1153
                true,
1154
                new class () {
1155
                    #[Number]
1156
                    public ?string $name = null;
1157
                },
1158
                true,
1159
            ],
1160
            'callable' => [
1161
                new WhenNull(),
1162
                new class () {
1163
                    #[Number]
1164
                    public ?string $name = null;
1165
                },
1166
                true,
1167
            ],
1168
            'do-not-override-rule' => [
1169
                false,
1170
                new class () {
1171
                    #[Number(skipOnEmpty: true)]
1172
                    public string $name = '';
1173
                },
1174
                true,
1175
            ],
1176
        ];
1177
    }
1178
1179
    /**
1180
     * @dataProvider initSkipOnEmptyDataProvider
1181
     */
1182
    public function testInitSkipOnEmpty(
1183
        bool|callable|null $skipOnEmpty,
1184
        mixed $data,
1185
        bool $expectedResult,
1186
    ): void {
1187
        $validator = new Validator(defaultSkipOnEmpty: $skipOnEmpty);
1188
1189
        $result = $validator->validate($data);
1190
1191
        $this->assertSame($expectedResult, $result->isValid());
1192
    }
1193
1194
    public function testObjectWithAttributesOnly(): void
1195
    {
1196
        $object = new ObjectWithAttributesOnly();
1197
1198
        $validator = new Validator();
1199
1200
        $result = $validator->validate($object);
1201
1202
        $this->assertFalse($result->isValid());
1203
        $this->assertCount(1, $result->getErrorMessages());
1204
        $this->assertStringStartsWith('The value must contain at least', $result->getErrorMessages()[0]);
1205
    }
1206
1207
    public function testRuleWithoutSkipOnEmpty(): void
1208
    {
1209
        $validator = new Validator(defaultSkipOnEmpty: new WhenNull());
1210
1211
        $data = new class () {
1212
            #[NotNull]
1213
            public ?string $name = null;
1214
        };
1215
1216
        $result = $validator->validate($data);
1217
1218
        $this->assertFalse($result->isValid());
1219
    }
1220
1221
    public function testValidateWithSingleRule(): void
1222
    {
1223
        $result = (new Validator())->validate(3, new Number(min: 5));
1224
1225
        $this->assertFalse($result->isValid());
1226
        $this->assertSame(
1227
            ['' => ['The value must be no less than 5.']],
1228
            $result->getErrorMessagesIndexedByPath(),
1229
        );
1230
    }
1231
1232
    public function testComposition(): void
1233
    {
1234
        $validator = new class () implements ValidatorInterface {
1235
            private Validator $validator;
1236
1237
            public function __construct()
1238
            {
1239
                $this->validator = new Validator();
1240
            }
1241
1242
            public function validate(
1243
                mixed $data,
1244
                callable|iterable|object|string|null $rules = null,
1245
                ?ValidationContext $context = null
1246
            ): Result {
1247
                $context ??= new ValidationContext();
1248
1249
                $result = $this->validator->validate($data, $rules, $context);
1250
1251
                return $context->getParameter('forceSuccess') === true ? new Result() : $result;
1252
            }
1253
        };
1254
1255
        $rules = [
1256
            static function ($value, $rule, ValidationContext $context) {
1257
                $context->setParameter('forceSuccess', true);
1258
                return (new Result())->addError('fail');
1259
            },
1260
        ];
1261
1262
        $result = $validator->validate([], $rules);
1263
1264
        $this->assertTrue($result->isValid());
1265
    }
1266
1267
    public function testRulesWithWrongKey(): void
1268
    {
1269
        $validator = new Validator();
1270
1271
        $this->expectException(InvalidArgumentException::class);
1272
        $this->expectExceptionMessage('An attribute can only have an integer or a string type. bool given.');
1273
        $validator->validate([], new IteratorWithBooleanKey());
1274
    }
1275
1276
    public function testRulesWithWrongRule(): void
1277
    {
1278
        $validator = new Validator();
1279
1280
        $this->expectException(InvalidArgumentException::class);
1281
        $message = 'Rule must be either an instance of Yiisoft\Validator\RuleInterface or a callable, int given.';
1282
        $this->expectExceptionMessage($message);
1283
        $validator->validate([], [new BooleanValue(), 1]);
1284
    }
1285
1286
    public function testRulesAsObjectNameWithRuleAttributes(): void
1287
    {
1288
        $validator = new Validator();
1289
        $result = $validator->validate(['name' => 'Test name'], ObjectWithAttributesOnly::class);
1290
        $this->assertTrue($result->isValid());
1291
    }
1292
1293
    public function testRulesAsObjectWithRuleAttributes(): void
1294
    {
1295
        $validator = new Validator();
1296
        $result = $validator->validate(['name' => 'Test name'], new ObjectWithAttributesOnly());
1297
        $this->assertTrue($result->isValid());
1298
    }
1299
1300
    public function testDataSetWithPostValidationHook(): void
1301
    {
1302
        $validator = new Validator();
1303
        $dataSet = new DataSetWithPostValidationHook();
1304
1305
        $result = $validator->validate($dataSet);
1306
1307
        $this->assertTrue($result->isValid());
1308
        $this->assertTrue($dataSet->hookCalled);
1309
    }
1310
1311
    public function testObjectWithPostValidationHook(): void
1312
    {
1313
        $validator = new Validator();
1314
        $object = new ObjectWithPostValidationHook();
1315
1316
        $result = $validator->validate($object);
1317
1318
        $this->assertTrue($result->isValid());
1319
        $this->assertTrue($object->hookCalled);
1320
    }
1321
1322
    public function testSkippingRuleInPreValidate(): void
1323
    {
1324
        $data = ['agree' => false, 'viewsCount' => -1];
1325
        $rules = [
1326
            'agree' => [new BooleanValue(skipOnEmpty: static fn (): bool => true), new TrueValue()],
1327
            'viewsCount' => [new Integer(min: 0)],
1328
        ];
1329
        $validator = new Validator();
1330
1331
        $result = $validator->validate($data, $rules);
1332
        $this->assertSame(
1333
            [
1334
                'agree' => ['The value must be "1".'],
1335
                'viewsCount' => ['The value must be no less than 0.'],
1336
            ],
1337
            $result->getErrorMessagesIndexedByPath(),
1338
        );
1339
    }
1340
1341
    public function testDefaultTranslatorWithIntl(): void
1342
    {
1343
        $data = ['number' => 3];
1344
        $rules = [
1345
            'number' => new Integer(
1346
                max: 2,
1347
                greaterThanMaxMessage: '{value, selectordinal, one{#-one} two{#-two} few{#-few} other{#-other}}',
1348
            ),
1349
        ];
1350
        $validator = new Validator();
1351
1352
        $result = $validator->validate($data, $rules);
1353
        $this->assertSame(['number' => ['3-few']], $result->getErrorMessagesIndexedByPath());
1354
    }
1355
1356
    public function dataSimpleForm(): array
1357
    {
1358
        return [
1359
            [
1360
                [
1361
                    'name' => [
1362
                        'Имя плохое.',
1363
                    ],
1364
                    'mail' => [
1365
                        'The value is not a valid email address.',
1366
                    ],
1367
                ],
1368
                null,
1369
            ],
1370
            [
1371
                [
1372
                    'name' => [
1373
                        'name плохое.',
1374
                    ],
1375
                    'mail' => [
1376
                        'The value is not a valid email address.',
1377
                    ],
1378
                ],
1379
                new ValidationContext(attributeTranslator: new NullAttributeTranslator()),
1380
            ],
1381
        ];
1382
    }
1383
1384
    /**
1385
     * @dataProvider dataSimpleForm
1386
     */
1387
    public function testSimpleForm(array $expectedMessages, ?ValidationContext $validationContext): void
1388
    {
1389
        $form = new SimpleForm();
1390
1391
        $result = (new Validator())->validate($form, context: $validationContext);
1392
1393
        $this->assertSame(
1394
            $expectedMessages,
1395
            $result->getErrorMessagesIndexedByPath()
1396
        );
1397
    }
1398
1399
    public function dataOriginalValueUsage(): array
1400
    {
1401
        $data = [
1402
            'null' => [null, null],
1403
            'string' => ['hello', 'hello'],
1404
            'integer' => [42, 42],
1405
            'array' => [['param' => 7], ['param' => 7]],
1406
            'array-data-set' => [['param' => 42], new ArrayDataSet(['param' => 42])],
1407
            'single-value-data-set' => [7, new SingleValueDataSet(7)],
1408
        ];
1409
1410
        $object = new stdClass();
1411
        $data['object'] = [$object, $object];
1412
1413
        $simpleDto = new SimpleDto();
1414
        $data['object-data-set'] = [$simpleDto, new ObjectDataSet($simpleDto)];
1415
1416
        return $data;
1417
    }
1418
1419
    /**
1420
     * @dataProvider dataOriginalValueUsage
1421
     */
1422
    public function testOriginalValueUsage(mixed $expectedValue, mixed $value): void
1423
    {
1424
        $valueHandled = false;
1425
        $valueInHandler = null;
1426
1427
        (new Validator())->validate(
1428
            $value,
1429
            static function ($value) use (&$valueHandled, &$valueInHandler): Result {
1430
                $valueHandled = true;
1431
                $valueInHandler = $value;
1432
                return new Result();
1433
            },
1434
        );
1435
1436
        $this->assertTrue($valueHandled);
1437
        $this->assertSame($expectedValue, $valueInHandler);
1438
    }
1439
1440
    public function testRuleWithBuiltInHandler(): void
1441
    {
1442
        $rule = new RuleWithBuiltInHandler();
1443
1444
        $result = (new Validator())->validate(19, $rule);
1445
1446
        $this->assertSame(
1447
            ['' => ['The value must be 42.']],
1448
            $result->getErrorMessagesIndexedByPath()
1449
        );
1450
    }
1451
1452
    public function testDifferentValueAsArrayInSameContext(): void
1453
    {
1454
        $result = (new Validator())->validate(
1455
            ['x' => ['a' => 1, 'b' => 2]],
1456
            [
1457
                new AtLeast(['x']),
1458
                'x' => new AtLeast(['a', 'b']),
1459
            ],
1460
        );
1461
        $this->assertTrue($result->isValid());
1462
    }
1463
1464
    public function dataErrorMessagesWithLabels(): array
1465
    {
1466
        return [
1467
            [
1468
                new class () {
1469
                    #[Label('Test')]
1470
                    #[Length(
1471
                        min: 20,
1472
                        lessThanMinMessage: '{attribute} value must contain at least {min, number} {min, plural, ' .
1473
                        'one{character} other{characters}}.',
1474
                    )]
1475
                    public string $property = 'test';
1476
                },
1477
                ['property' => ['Test value must contain at least 20 characters.']],
1478
            ],
1479
            [
1480
                new class () {
1481
                    #[Label]
1482
                    #[Length(min: 20)]
1483
                    public string $property = 'test';
1484
                },
1485
                ['property' => ['property must contain at least 20 characters.']],
1486
            ],
1487
            [
1488
                new #[Label('This value')] class () {
0 ignored issues
show
Bug introduced by
A parse error occurred: Syntax error, unexpected T_ATTRIBUTE_START on line 1488 at column 20
Loading history...
1489
                    #[Length(min: 20)]
1490
                    public string $property = 'test';
1491
                },
1492
                ['property' => ['This value must contain at least 20 characters.']],
1493
            ],
1494
            [
1495
                new #[Label] class () {
1496
                    #[Length(min: 20)]
1497
                    public string $property = 'test';
1498
                },
1499
                ['property' => ['property must contain at least 20 characters.']],
1500
            ],
1501
            [
1502
                new ObjectWithLabelsProvider(),
1503
                [
1504
                    'name' => ['Имя cannot be blank.'],
1505
                    'age' => ['Возраст must be no less than 21.'],
1506
                ],
1507
            ],
1508
        ];
1509
    }
1510
1511
    /**
1512
     * @dataProvider dataErrorMessagesWithLabels
1513
     */
1514
    public function testErrorMessagesWithLabels(
1515
        mixed $data,
1516
        array $expectedErrorMessages,
1517
    ): void {
1518
        $validator = new Validator();
1519
        $result = $validator->validate($data, $data);
1520
        $this->assertSame($expectedErrorMessages, $result->getErrorMessagesIndexedByAttribute());
1521
    }
1522
}
1523