Issues (138)

tests/DataSet/ObjectDataSetTest.php (2 issues)

1
<?php
2
3
declare(strict_types=1);
4
5
namespace Yiisoft\Validator\Tests\DataSet;
6
7
use InvalidArgumentException;
8
use PHPUnit\Framework\TestCase;
9
use ReflectionProperty;
10
use stdClass;
11
use Traversable;
12
use Yiisoft\Validator\DataSet\ObjectDataSet;
13
use Yiisoft\Validator\Label;
14
use Yiisoft\Validator\Rule\Callback;
15
use Yiisoft\Validator\Rule\Equal;
16
use Yiisoft\Validator\Rule\Length;
17
use Yiisoft\Validator\Rule\Required;
18
use Yiisoft\Validator\RuleInterface;
19
use Yiisoft\Validator\Tests\Support\Data\ObjectWithCallbackMethod\ObjectWithCallbackMethod;
20
use Yiisoft\Validator\Tests\Support\Data\ObjectWithCallbackMethod\ObjectWithNonExistingCallbackMethod;
21
use Yiisoft\Validator\Tests\Support\Data\ObjectWithDataSet;
22
use Yiisoft\Validator\Tests\Support\Data\ObjectWithDataSetAndRulesProvider;
23
use Yiisoft\Validator\Tests\Support\Data\ObjectWithDifferentPropertyVisibility;
24
use Yiisoft\Validator\Tests\Support\Data\ObjectWithDynamicDataSet;
25
use Yiisoft\Validator\Tests\Support\Data\ObjectWithLabelsProvider;
26
use Yiisoft\Validator\Tests\Support\Data\ObjectWithRulesProvider;
27
use Yiisoft\Validator\Tests\Support\Data\Post;
28
use Yiisoft\Validator\Tests\Support\Data\TitleTrait;
29
use Yiisoft\Validator\Tests\Support\Rule\NotRuleAttribute;
30
use Yiisoft\Validator\Validator;
31
32
final class ObjectDataSetTest extends TestCase
33
{
34
    public function propertyVisibilityDataProvider(): array
35
    {
36
        return [
37
            [
38
                new ObjectDataSet(new ObjectWithDifferentPropertyVisibility()),
39
                ['name' => '', 'age' => 17, 'number' => 42],
40
                ['name' => '', 'age' => 17, 'number' => 42, 'non-exist' => null],
41
                ['name', 'age', 'number'],
42
            ],
43
            [
44
                new ObjectDataSet(new ObjectWithDifferentPropertyVisibility(), ReflectionProperty::IS_PRIVATE),
45
                ['number' => 42],
46
                ['name' => null, 'age' => null, 'number' => 42, 'non-exist' => null],
47
                ['number'],
48
            ],
49
            [
50
                new ObjectDataSet(new ObjectWithDifferentPropertyVisibility(), ReflectionProperty::IS_PROTECTED),
51
                ['age' => 17],
52
                ['name' => null, 'age' => 17, 'number' => null, 'non-exist' => null],
53
                ['age'],
54
            ],
55
            [
56
                new ObjectDataSet(new ObjectWithDifferentPropertyVisibility(), ReflectionProperty::IS_PUBLIC),
57
                ['name' => ''],
58
                ['name' => '', 'age' => null, 'number' => null, 'non-exist' => null],
59
                ['name'],
60
            ],
61
            [
62
                new ObjectDataSet(
63
                    new ObjectWithDifferentPropertyVisibility(),
64
                    ReflectionProperty::IS_PUBLIC | ReflectionProperty::IS_PROTECTED
65
                ),
66
                ['name' => '', 'age' => 17],
67
                ['name' => '', 'age' => 17, 'number' => null, 'non-exist' => null],
68
                ['name', 'age'],
69
            ],
70
        ];
71
    }
72
73
    /**
74
     * @dataProvider propertyVisibilityDataProvider
75
     */
76
    public function testPropertyVisibility(
77
        ObjectDataSet $initialDataSet,
78
        array $expectedData,
79
        array $expectedAttributeValuesMap,
80
        array $expectedRulesKeys
81
    ): void {
82
        $dataSets = [
83
            $initialDataSet,
84
            $initialDataSet, // Not a duplicate. Used to test caching.
85
        ];
86
        foreach ($dataSets as $dataSet) {
87
            /** @var ObjectDataSet $dataSet */
88
89
            $this->assertSame($expectedData, $dataSet->getData());
90
91
            foreach ($expectedAttributeValuesMap as $attribute => $value) {
92
                $this->assertSame($value, $dataSet->getAttributeValue($attribute));
93
            }
94
95
            $this->assertSame($expectedRulesKeys, array_keys($dataSet->getRules()));
96
        }
97
    }
98
99
    public function objectWithDataSetDataProvider(): array
100
    {
101
        $dataSet = new ObjectDataSet(new ObjectWithDataSet());
102
103
        return [
104
            [new ObjectDataSet(new ObjectWithDataSet())],
105
            [new ObjectDataSet(new ObjectWithDataSet())], // Not a duplicate. Used to test caching.
106
            [$dataSet],
107
            [$dataSet], // Not a duplicate. Used to test caching.
108
        ];
109
    }
110
111
    /**
112
     * @dataProvider objectWithDataSetDataProvider
113
     */
114
    public function testObjectWithDataSet(ObjectDataSet $dataSet): void
115
    {
116
        $this->assertSame(['key1' => 7, 'key2' => 42], $dataSet->getData());
117
        $this->assertSame(7, $dataSet->getAttributeValue('key1'));
118
        $this->assertSame(42, $dataSet->getAttributeValue('key2'));
119
120
        $this->assertNull($dataSet->getAttributeValue('name'));
121
        $this->assertNull($dataSet->getAttributeValue('age'));
122
        $this->assertNull($dataSet->getAttributeValue('number'));
123
        $this->assertNull($dataSet->getAttributeValue('non-exist'));
124
125
        $this->assertSame([], $dataSet->getRules());
126
    }
127
128
    public function objectWithRulesProvider(): array
129
    {
130
        $dataSet = new ObjectDataSet(new ObjectWithRulesProvider());
131
132
        return [
133
            [new ObjectDataSet(new ObjectWithRulesProvider())],
134
            [new ObjectDataSet(new ObjectWithRulesProvider())], // Not a duplicate. Used to test caching.
135
            [$dataSet],
136
            [$dataSet], // Not a duplicate. Used to test caching.
137
        ];
138
    }
139
140
    /**
141
     * @dataProvider objectWithRulesProvider
142
     */
143
    public function testObjectWithRulesProvider(ObjectDataSet $dataSet): void
144
    {
145
        $rules = $dataSet->getRules();
146
147
        $this->assertSame(['name' => '', 'age' => 17, 'number' => 42], $dataSet->getData());
148
149
        $this->assertSame('', $dataSet->getAttributeValue('name'));
150
        $this->assertSame(17, $dataSet->getAttributeValue('age'));
151
        $this->assertSame(42, $dataSet->getAttributeValue('number'));
152
        $this->assertNull($dataSet->getAttributeValue('non-exist'));
153
154
        $this->assertSame(['age'], array_keys($rules));
155
        $this->assertCount(2, $rules['age']);
156
        $this->assertInstanceOf(Required::class, $rules['age'][0]);
157
        $this->assertInstanceOf(Equal::class, $rules['age'][1]);
158
    }
159
160
    public function objectWithDataSetAndRulesProviderDataProvider(): array
161
    {
162
        $dataSet = new ObjectDataSet(new ObjectWithDataSetAndRulesProvider());
163
164
        return [
165
            [new ObjectDataSet(new ObjectWithDataSetAndRulesProvider())],
166
            [new ObjectDataSet(new ObjectWithDataSetAndRulesProvider())], // Not a duplicate. Used to test caching.
167
            [$dataSet],
168
            [$dataSet], // Not a duplicate. Used to test caching.
169
        ];
170
    }
171
172
    /**
173
     * @dataProvider objectWithDataSetAndRulesProviderDataProvider
174
     */
175
    public function testObjectWithDataSetAndRulesProvider(ObjectDataSet $dataSet): void
176
    {
177
        $rules = $dataSet->getRules();
178
179
        $this->assertSame(['key1' => 7, 'key2' => 42], $dataSet->getData());
180
        $this->assertSame(7, $dataSet->getAttributeValue('key1'));
181
        $this->assertSame(42, $dataSet->getAttributeValue('key2'));
182
183
        $this->assertNull($dataSet->getAttributeValue('name'));
184
        $this->assertNull($dataSet->getAttributeValue('age'));
185
        $this->assertNull($dataSet->getAttributeValue('number'));
186
        $this->assertNull($dataSet->getAttributeValue('non-exist'));
187
188
        $this->assertSame(['key1', 'key2'], array_keys($rules));
189
        $this->assertCount(1, $rules['key1']);
190
        $this->assertInstanceOf(Required::class, $rules['key1'][0]);
191
        $this->assertCount(2, $rules['key2']);
192
        $this->assertInstanceOf(Required::class, $rules['key2'][0]);
193
        $this->assertInstanceOf(Equal::class, $rules['key2'][1]);
194
    }
195
196
    /**
197
     * @dataProvider dataCollectRules
198
     *
199
     * @param RuleInterface[]|RuleInterface[][]|RuleInterface[][][] $expectedRules
200
     */
201
    public function testCollectRules(object $object, array $expectedRules): void
202
    {
203
        $dataSet = new ObjectDataSet($object);
204
205
        $actualRules = [];
206
        foreach ($dataSet->getRules() as $attribute => $rules) {
207
            $actualRules[$attribute] = $rules instanceof Traversable ? iterator_to_array($rules) : (array) $rules;
208
        }
209
210
        $this->assertEquals($expectedRules, $actualRules);
211
    }
212
213
    public function dataCollectRules(): array
214
    {
215
        return [
216
            [
217
                new class () {
218
                },
219
                [],
220
            ],
221
            [
222
                new class () {
223
                    private $property1;
0 ignored issues
show
The private property $property1 is not used, and could be removed.
Loading history...
224
                },
225
                [],
226
            ],
227
            [
228
                new class () {
229
                    #[NotRuleAttribute]
230
                    private $property1;
231
                },
232
                [],
233
            ],
234
            [
235
                new class () {
236
                    #[Required()]
237
                    private $property1;
238
                },
239
                [
240
                    'property1' => [
241
                        new Required(),
242
                    ],
243
                ],
244
            ],
245
            [
246
                new class () {
247
                    use TitleTrait;
248
                },
249
                [
250
                    'title' => [
251
                        new Length(max: 255),
252
                    ],
253
                ],
254
            ],
255
            [
256
                new class () {
257
                    #[Required()]
258
                    #[Length(max: 255, skipOnEmpty: true)]
259
                    private $property1;
260
                    #[Required()]
261
                    #[Length(max: 255, skipOnEmpty: true)]
262
                    private $property2;
0 ignored issues
show
The private property $property2 is not used, and could be removed.
Loading history...
263
                },
264
                [
265
                    'property1' => [
266
                        new Required(),
267
                        new Length(max: 255, skipOnEmpty: true),
268
                    ],
269
                    'property2' => [
270
                        new Required(),
271
                        new Length(max: 255, skipOnEmpty: true),
272
                    ],
273
                ],
274
            ],
275
            [
276
                new class () {
277
                    #[Length(max: 255, skipOnEmpty: true)]
278
                    #[Length(max: 255, skipOnEmpty: false)]
279
                    private $property1;
280
                },
281
                [
282
                    'property1' => [
283
                        new Length(max: 255, skipOnEmpty: true),
284
                        new Length(max: 255, skipOnEmpty: false),
285
                    ],
286
                ],
287
            ],
288
        ];
289
    }
290
291
    /**
292
     * @link https://github.com/yiisoft/validator/issues/198
293
     */
294
    public function testGetRulesViaTraits(): void
295
    {
296
        $dataSet = new ObjectDataSet(new Post());
297
        $expectedRules = ['title' => [new Length(max: 255)]];
298
299
        $this->assertEquals($expectedRules, $dataSet->getRules());
300
    }
301
302
    /**
303
     * @link https://github.com/yiisoft/validator/issues/223
304
     */
305
    public function testValidateWithCallbackMethod(): void
306
    {
307
        $dataSet = new ObjectDataSet(new ObjectWithCallbackMethod());
308
        $validator = new Validator();
309
310
        /** @var array $rules */
311
        $rules = $dataSet->getRules();
312
        $this->assertSame(['name'], array_keys($rules));
313
        $this->assertCount(1, $rules['name']);
314
        $this->assertInstanceOf(Callback::class, $rules['name'][0]);
315
316
        $result = $validator->validate(['name' => 'bar'], $rules);
317
        $this->assertSame(['name' => ['Value must be "foo"!']], $result->getErrorMessagesIndexedByPath());
318
    }
319
320
    public function testValidateWithWrongCallbackMethod(): void
321
    {
322
        $object = new ObjectWithNonExistingCallbackMethod();
323
        $dataSet = new ObjectDataSet($object);
324
325
        $this->expectException(InvalidArgumentException::class);
326
        $this->expectExceptionMessage(
327
            sprintf(
328
                'Method "%s" does not exist in class "%s".',
329
                'validateName',
330
                $object::class,
331
            )
332
        );
333
        $dataSet->getRules();
334
    }
335
336
    public function objectWithDynamicDataSetProvider(): array
337
    {
338
        return [
339
            [
340
                new ObjectDataSet(new ObjectWithDynamicDataSet('A')),
341
                ['name' => 'A'],
342
            ],
343
            [
344
                new ObjectDataSet(new ObjectWithDynamicDataSet('B')),
345
                ['name' => 'B'],
346
            ],
347
        ];
348
    }
349
350
    /**
351
     * @dataProvider objectWithDynamicDataSetProvider
352
     */
353
    public function testObjectWithDynamicDataSet(ObjectDataSet $dataSet, array $expectedData): void
354
    {
355
        $this->assertSame($expectedData, $dataSet->getData());
356
    }
357
358
    public function testWithoutCache(): void
359
    {
360
        $object1 = new stdClass();
361
        $object1->a = 4;
362
        $dataSet1 = new ObjectDataSet($object1, useCache: false);
363
364
        $object2 = new stdClass();
365
        $object2->b = 2;
366
        $dataSet2 = new ObjectDataSet($object2, useCache: false);
367
368
        $this->assertSame(['a' => 4], $dataSet1->getData());
369
        $this->assertSame(['b' => 2], $dataSet2->getData());
370
    }
371
372
    public function testHasAttributeWithDataSetProvided(): void
373
    {
374
        $objectDataSet = new ObjectDataSet(new ObjectWithDataSet());
375
        $this->assertTrue($objectDataSet->hasAttribute('key1'));
376
        $this->assertFalse($objectDataSet->hasAttribute('non-existing-key'));
377
    }
378
379
    public function objectWithLabelsProvider(): array
380
    {
381
        $dataSet = new ObjectDataSet(new ObjectWithLabelsProvider());
382
        $expectedResult = ['name' => 'Имя', 'age' => 'Возраст'];
383
384
        return [
385
            [new ObjectDataSet(new ObjectWithLabelsProvider()), $expectedResult],
386
            [new ObjectDataSet(new ObjectWithLabelsProvider()), $expectedResult], // Not a duplicate. Used to test caching.
387
            [$dataSet, $expectedResult],
388
            [$dataSet, $expectedResult], // Not a duplicate. Used to test caching.
389
            [
390
                new ObjectDataSet(new class () {
391
                    #[Required]
392
                    #[Label('Test label')]
393
                    public string $property;
394
                }),
395
                ['property' => 'Test label'],
396
            ],
397
        ];
398
    }
399
400
    /**
401
     * @dataProvider objectWithLabelsProvider
402
     */
403
    public function testObjectWithLabelsProvider(ObjectDataSet $dataSet, array $expected): void
404
    {
405
        $this->assertSame($expected, $dataSet->getValidationPropertyLabels());
406
    }
407
}
408