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
introduced
by
![]() |
|||
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
|
|||
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 |