Passed
Pull Request — main (#27)
by Anatoly
41:39
created

HydratorTest.php$0 ➔ testAnnotationReader()   A

Complexity

Conditions 1

Size

Total Lines 14

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 14
rs 9.7998
cc 1

1 Method

Rating   Name   Duplication   Size   Complexity  
A HydratorTest::testHydrateRelationshipsPropertyWithNotArray() 0 7 1
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Sunrise\Hydrator\Tests;
6
7
use PHPUnit\Framework\TestCase;
8
use Sunrise\Hydrator\Annotation\Format;
9
use Sunrise\Hydrator\Dictionary\ErrorCode;
10
use Sunrise\Hydrator\Exception\InvalidDataException;
11
use Sunrise\Hydrator\Exception\UninitializableObjectException;
12
use Sunrise\Hydrator\Exception\UnsupportedPropertyTypeException;
13
use Sunrise\Hydrator\Exception\UntypedPropertyException;
14
use Sunrise\Hydrator\Hydrator;
15
use Sunrise\Hydrator\HydratorInterface;
16
use Sunrise\Hydrator\Tests\Fixtures\IntegerEnum;
17
use Sunrise\Hydrator\Tests\Fixtures\Issue25;
18
use Sunrise\Hydrator\Tests\Fixtures\ObjectWithAnnotatedAlias;
19
use Sunrise\Hydrator\Tests\Fixtures\ObjectWithArray;
20
use Sunrise\Hydrator\Tests\Fixtures\ObjectWithAttributedAlias;
21
use Sunrise\Hydrator\Tests\Fixtures\ObjectWithBoolean;
22
use Sunrise\Hydrator\Tests\Fixtures\ObjectWithCollection;
23
use Sunrise\Hydrator\Tests\Fixtures\ObjectWithIgnoredProperty;
24
use Sunrise\Hydrator\Tests\Fixtures\ObjectWithInteger;
25
use Sunrise\Hydrator\Tests\Fixtures\ObjectWithIntegerEnum;
26
use Sunrise\Hydrator\Tests\Fixtures\ObjectWithNullableArray;
27
use Sunrise\Hydrator\Tests\Fixtures\ObjectWithNullableBoolean;
28
use Sunrise\Hydrator\Tests\Fixtures\ObjectWithNullableCollection;
29
use Sunrise\Hydrator\Tests\Fixtures\ObjectWithNullableInteger;
30
use Sunrise\Hydrator\Tests\Fixtures\ObjectWithNullableIntegerEnum;
31
use Sunrise\Hydrator\Tests\Fixtures\ObjectWithNullableNumber;
32
use Sunrise\Hydrator\Tests\Fixtures\ObjectWithNullableRelationship;
33
use Sunrise\Hydrator\Tests\Fixtures\ObjectWithNullableRelationships;
34
use Sunrise\Hydrator\Tests\Fixtures\ObjectWithNullableString;
35
use Sunrise\Hydrator\Tests\Fixtures\ObjectWithNullableStringEnum;
36
use Sunrise\Hydrator\Tests\Fixtures\ObjectWithNullableTimestamp;
37
use Sunrise\Hydrator\Tests\Fixtures\ObjectWithNullableTimezone;
38
use Sunrise\Hydrator\Tests\Fixtures\ObjectWithNullableUid;
39
use Sunrise\Hydrator\Tests\Fixtures\ObjectWithNullableUnixTimeStamp;
40
use Sunrise\Hydrator\Tests\Fixtures\ObjectWithNumber;
41
use Sunrise\Hydrator\Tests\Fixtures\ObjectWithOptionalArray;
42
use Sunrise\Hydrator\Tests\Fixtures\ObjectWithOptionalBoolean;
43
use Sunrise\Hydrator\Tests\Fixtures\ObjectWithOptionalCollection;
44
use Sunrise\Hydrator\Tests\Fixtures\ObjectWithOptionalInteger;
45
use Sunrise\Hydrator\Tests\Fixtures\ObjectWithOptionalIntegerEnum;
46
use Sunrise\Hydrator\Tests\Fixtures\ObjectWithOptionalNumber;
47
use Sunrise\Hydrator\Tests\Fixtures\ObjectWithOptionalRelationship;
48
use Sunrise\Hydrator\Tests\Fixtures\ObjectWithOptionalRelationships;
49
use Sunrise\Hydrator\Tests\Fixtures\ObjectWithOptionalString;
50
use Sunrise\Hydrator\Tests\Fixtures\ObjectWithOptionalStringEnum;
51
use Sunrise\Hydrator\Tests\Fixtures\ObjectWithOptionalTimestamp;
52
use Sunrise\Hydrator\Tests\Fixtures\ObjectWithOptionalTimezone;
53
use Sunrise\Hydrator\Tests\Fixtures\ObjectWithOptionalUid;
54
use Sunrise\Hydrator\Tests\Fixtures\ObjectWithOptionalUnixTimeStamp;
55
use Sunrise\Hydrator\Tests\Fixtures\ObjectWithRelationship;
56
use Sunrise\Hydrator\Tests\Fixtures\ObjectWithRelationshipWithNullableString;
57
use Sunrise\Hydrator\Tests\Fixtures\ObjectWithRelationshipWithOptionalString;
58
use Sunrise\Hydrator\Tests\Fixtures\ObjectWithRelationshipWithString;
59
use Sunrise\Hydrator\Tests\Fixtures\ObjectWithRelationships;
60
use Sunrise\Hydrator\Tests\Fixtures\ObjectWithRelationshipsWithLimit;
61
use Sunrise\Hydrator\Tests\Fixtures\ObjectWithRelationshipsWithUnstantiableObject;
62
use Sunrise\Hydrator\Tests\Fixtures\ObjectWithStaticalProperty;
63
use Sunrise\Hydrator\Tests\Fixtures\ObjectWithString;
64
use Sunrise\Hydrator\Tests\Fixtures\ObjectWithStringEnum;
65
use Sunrise\Hydrator\Tests\Fixtures\ObjectWithTimestamp;
66
use Sunrise\Hydrator\Tests\Fixtures\ObjectWithTimezone;
67
use Sunrise\Hydrator\Tests\Fixtures\ObjectWithUid;
68
use Sunrise\Hydrator\Tests\Fixtures\ObjectWithUnformattedTimestampProperty;
69
use Sunrise\Hydrator\Tests\Fixtures\ObjectWithUnixTimeStamp;
70
use Sunrise\Hydrator\Tests\Fixtures\ObjectWithUnstantiableRelationship;
71
use Sunrise\Hydrator\Tests\Fixtures\ObjectWithUnsupportedProperty;
72
use Sunrise\Hydrator\Tests\Fixtures\ObjectWithUnsupportedPropertyNotation;
73
use Sunrise\Hydrator\Tests\Fixtures\ObjectWithUntypedProperty;
74
use Sunrise\Hydrator\Tests\Fixtures\Store\Product;
75
use Sunrise\Hydrator\Tests\Fixtures\Store\Status;
76
use Sunrise\Hydrator\Tests\Fixtures\StringEnum;
77
use Sunrise\Hydrator\Tests\Fixtures\UnstantiableObject;
78
79
use function sprintf;
80
use function version_compare;
81
82
use const PHP_VERSION;
83
use const PHP_VERSION_ID;
84
85
class HydratorTest extends TestCase
86
{
87
    private ?int $invalidValueExceptionCount = null;
88
    private array $invalidValueExceptionMessage = [];
89
    private array $invalidValueExceptionPropertyPath = [];
90
    private array $invalidValueExceptionErrorCode = [];
91
92
    /**
93
     * @group boolean
94
     * @dataProvider strictBooleanDataProvider
95
     * @dataProvider nonStrictBooleanDataProvider
96
     */
97
    public function testHydrateBooleanProperty(array $data, bool $expected): void
98
    {
99
        $this->assertInvalidValueExceptionCount(0);
100
        $object = $this->createHydrator()->hydrate(ObjectWithBoolean::class, $data);
101
        $this->assertSame($expected, $object->value);
102
    }
103
104
    /**
105
     * @group boolean
106
     * @dataProvider strictBooleanDataProvider
107
     * @dataProvider nonStrictBooleanDataProvider
108
     * @dataProvider strictNullDataProvider
109
     * @dataProvider nonStrictNullDataProvider
110
     */
111
    public function testHydrateNullableBooleanProperty(array $data, ?bool $expected): void
112
    {
113
        $this->assertInvalidValueExceptionCount(0);
114
        $object = $this->createHydrator()->hydrate(ObjectWithNullableBoolean::class, $data);
115
        $this->assertSame($expected, $object->value);
116
    }
117
118
    /**
119
     * @group boolean
120
     * @dataProvider strictBooleanDataProvider
121
     * @dataProvider nonStrictBooleanDataProvider
122
     * @dataProvider emptyDataProvider
123
     */
124
    public function testHydrateOptionalBooleanProperty(array $data, bool $expected = true): void
125
    {
126
        $this->assertInvalidValueExceptionCount(0);
127
        $object = $this->createHydrator()->hydrate(ObjectWithOptionalBoolean::class, $data);
128
        $this->assertSame($expected, $object->value);
129
    }
130
131
    /**
132
     * @group boolean
133
     * @dataProvider strictNullDataProvider
134
     * @dataProvider nonStrictNullDataProvider
135
     */
136
    public function testHydrateNonNullableBooleanPropertyWithNull(array $data): void
137
    {
138
        $this->assertInvalidValueExceptionCount(1);
139
        $this->assertInvalidValueExceptionMessage(0, 'This value should not be empty.');
140
        $this->assertInvalidValueExceptionErrorCode(0, ErrorCode::VALUE_SHOULD_NOT_BE_EMPTY);
141
        $this->assertInvalidValueExceptionPropertyPath(0, 'value');
142
        $this->createHydrator()->hydrate(ObjectWithBoolean::class, $data);
143
    }
144
145
    /**
146
     * @group boolean
147
     * @dataProvider notBooleanDataProvider
148
     */
149
    public function testHydrateBooleanPropertyWithNonBooleanValue(array $data): void
150
    {
151
        $this->assertInvalidValueExceptionCount(1);
152
        $this->assertInvalidValueExceptionMessage(0, 'This value should be of type boolean.');
153
        $this->assertInvalidValueExceptionErrorCode(0, ErrorCode::VALUE_SHOULD_BE_BOOLEAN);
154
        $this->assertInvalidValueExceptionPropertyPath(0, 'value');
155
        $this->createHydrator()->hydrate(ObjectWithBoolean::class, $data);
156
    }
157
158
    /**
159
     * @group boolean
160
     */
161
    public function testHydrateRequiredBooleanPropertyWithoutValue(): void
162
    {
163
        $this->assertInvalidValueExceptionCount(1);
164
        $this->assertInvalidValueExceptionMessage(0, 'This value should be provided.');
165
        $this->assertInvalidValueExceptionErrorCode(0, ErrorCode::VALUE_SHOULD_BE_PROVIDED);
166
        $this->assertInvalidValueExceptionPropertyPath(0, 'value');
167
        $this->createHydrator()->hydrate(ObjectWithBoolean::class, []);
168
    }
169
170
    /**
171
     * @group integer
172
     * @dataProvider strictIntegerDataProvider
173
     * @dataProvider nonStrictIntegerDataProvider
174
     */
175
    public function testHydrateIntegerProperty(array $data, int $expected): void
176
    {
177
        $this->assertInvalidValueExceptionCount(0);
178
        $object = $this->createHydrator()->hydrate(ObjectWithInteger::class, $data);
179
        $this->assertSame($expected, $object->value);
180
    }
181
182
    /**
183
     * @group integer
184
     * @dataProvider strictIntegerDataProvider
185
     * @dataProvider nonStrictIntegerDataProvider
186
     * @dataProvider strictNullDataProvider
187
     * @dataProvider nonStrictNullDataProvider
188
     */
189
    public function testHydrateNullableIntegerProperty(array $data, ?int $expected): void
190
    {
191
        $this->assertInvalidValueExceptionCount(0);
192
        $object = $this->createHydrator()->hydrate(ObjectWithNullableInteger::class, $data);
193
        $this->assertSame($expected, $object->value);
194
    }
195
196
    /**
197
     * @group integer
198
     * @dataProvider strictIntegerDataProvider
199
     * @dataProvider nonStrictIntegerDataProvider
200
     * @dataProvider emptyDataProvider
201
     */
202
    public function testHydrateOptionalIntegerProperty(array $data, int $expected = 42): void
203
    {
204
        $this->assertInvalidValueExceptionCount(0);
205
        $object = $this->createHydrator()->hydrate(ObjectWithOptionalInteger::class, $data);
206
        $this->assertSame($expected, $object->value);
207
    }
208
209
    /**
210
     * @group integer
211
     * @dataProvider strictNullDataProvider
212
     * @dataProvider nonStrictNullDataProvider
213
     */
214
    public function testHydrateNonNullableIntegerPropertyWithNull(array $data): void
215
    {
216
        $this->assertInvalidValueExceptionCount(1);
217
        $this->assertInvalidValueExceptionMessage(0, 'This value should not be empty.');
218
        $this->assertInvalidValueExceptionErrorCode(0, ErrorCode::VALUE_SHOULD_NOT_BE_EMPTY);
219
        $this->assertInvalidValueExceptionPropertyPath(0, 'value');
220
        $this->createHydrator()->hydrate(ObjectWithInteger::class, $data);
221
    }
222
223
    /**
224
     * @group integer
225
     * @dataProvider notIntegerDataProvider
226
     */
227
    public function testHydrateIntegerPropertyWithNotInteger(array $data): void
228
    {
229
        $this->assertInvalidValueExceptionCount(1);
230
        $this->assertInvalidValueExceptionMessage(0, 'This value should be of type integer.');
231
        $this->assertInvalidValueExceptionErrorCode(0, ErrorCode::VALUE_SHOULD_BE_INTEGER);
232
        $this->assertInvalidValueExceptionPropertyPath(0, 'value');
233
        $this->createHydrator()->hydrate(ObjectWithInteger::class, $data);
234
    }
235
236
    /**
237
     * @group integer
238
     */
239
    public function testHydrateRequiredIntegerPropertyWithoutValue(): void
240
    {
241
        $this->assertInvalidValueExceptionCount(1);
242
        $this->assertInvalidValueExceptionMessage(0, 'This value should be provided.');
243
        $this->assertInvalidValueExceptionErrorCode(0, ErrorCode::VALUE_SHOULD_BE_PROVIDED);
244
        $this->assertInvalidValueExceptionPropertyPath(0, 'value');
245
        $this->createHydrator()->hydrate(ObjectWithInteger::class, []);
246
    }
247
248
    /**
249
     * @group number
250
     * @dataProvider strictNumberDataProvider
251
     * @dataProvider nonStrictNumberDataProvider
252
     */
253
    public function testHydrateNumericProperty(array $data, float $expected): void
254
    {
255
        $this->assertInvalidValueExceptionCount(0);
256
        $object = $this->createHydrator()->hydrate(ObjectWithNumber::class, $data);
257
        $this->assertSame($expected, $object->value);
258
    }
259
260
    /**
261
     * @group number
262
     * @dataProvider strictNumberDataProvider
263
     * @dataProvider nonStrictNumberDataProvider
264
     * @dataProvider strictNullDataProvider
265
     * @dataProvider nonStrictNullDataProvider
266
     */
267
    public function testHydrateNullableNumericProperty(array $data, ?float $expected): void
268
    {
269
        $this->assertInvalidValueExceptionCount(0);
270
        $object = $this->createHydrator()->hydrate(ObjectWithNullableNumber::class, $data);
271
        $this->assertSame($expected, $object->value);
272
    }
273
274
    /**
275
     * @group number
276
     * @dataProvider strictNumberDataProvider
277
     * @dataProvider nonStrictNumberDataProvider
278
     * @dataProvider emptyDataProvider
279
     */
280
    public function testHydrateOptionalNumericProperty(array $data, float $expected = 3.14159): void
281
    {
282
        $this->assertInvalidValueExceptionCount(0);
283
        $object = $this->createHydrator()->hydrate(ObjectWithOptionalNumber::class, $data);
284
        $this->assertSame($expected, $object->value);
285
    }
286
287
    /**
288
     * @group number
289
     * @dataProvider strictNullDataProvider
290
     * @dataProvider nonStrictNullDataProvider
291
     */
292
    public function testHydrateNonNullableNumericPropertyWithNull(array $data): void
293
    {
294
        $this->assertInvalidValueExceptionCount(1);
295
        $this->assertInvalidValueExceptionMessage(0, 'This value should not be empty.');
296
        $this->assertInvalidValueExceptionErrorCode(0, ErrorCode::VALUE_SHOULD_NOT_BE_EMPTY);
297
        $this->assertInvalidValueExceptionPropertyPath(0, 'value');
298
        $this->createHydrator()->hydrate(ObjectWithNumber::class, $data);
299
    }
300
301
    /**
302
     * @group number
303
     * @dataProvider notNumberDataProvider
304
     */
305
    public function testHydrateNumericPropertyWithNotNumber(array $data): void
306
    {
307
        $this->assertInvalidValueExceptionCount(1);
308
        $this->assertInvalidValueExceptionMessage(0, 'This value should be of type number.');
309
        $this->assertInvalidValueExceptionErrorCode(0, ErrorCode::VALUE_SHOULD_BE_NUMBER);
310
        $this->assertInvalidValueExceptionPropertyPath(0, 'value');
311
        $this->createHydrator()->hydrate(ObjectWithNumber::class, $data);
312
    }
313
314
    /**
315
     * @group number
316
     */
317
    public function testHydrateRequiredNumericPropertyWithoutValue(): void
318
    {
319
        $this->assertInvalidValueExceptionCount(1);
320
        $this->assertInvalidValueExceptionMessage(0, 'This value should be provided.');
321
        $this->assertInvalidValueExceptionErrorCode(0, ErrorCode::VALUE_SHOULD_BE_PROVIDED);
322
        $this->assertInvalidValueExceptionPropertyPath(0, 'value');
323
        $this->createHydrator()->hydrate(ObjectWithNumber::class, []);
324
    }
325
326
    /**
327
     * @group string
328
     * @dataProvider stringDataProvider
329
     */
330
    public function testHydrateStringProperty(array $data, string $expected): void
331
    {
332
        $this->assertInvalidValueExceptionCount(0);
333
        $object = $this->createHydrator()->hydrate(ObjectWithString::class, $data);
334
        $this->assertSame($expected, $object->value);
335
    }
336
337
    /**
338
     * @group string
339
     * @dataProvider stringDataProvider
340
     * @dataProvider strictNullDataProvider
341
     */
342
    public function testHydrateNullableStringProperty(array $data, ?string $expected): void
343
    {
344
        $this->assertInvalidValueExceptionCount(0);
345
        $object = $this->createHydrator()->hydrate(ObjectWithNullableString::class, $data);
346
        $this->assertSame($expected, $object->value);
347
    }
348
349
    /**
350
     * @group string
351
     * @dataProvider stringDataProvider
352
     * @dataProvider emptyDataProvider
353
     */
354
    public function testHydrateOptionalStringProperty(array $data, string $expected = 'default'): void
355
    {
356
        $this->assertInvalidValueExceptionCount(0);
357
        $object = $this->createHydrator()->hydrate(ObjectWithOptionalString::class, $data);
358
        $this->assertSame($expected, $object->value);
359
    }
360
361
    /**
362
     * @group string
363
     * @dataProvider strictNullDataProvider
364
     */
365
    public function testHydrateNonNullableStringPropertyWithNull(array $data): void
366
    {
367
        $this->assertInvalidValueExceptionCount(1);
368
        $this->assertInvalidValueExceptionMessage(0, 'This value should not be empty.');
369
        $this->assertInvalidValueExceptionErrorCode(0, ErrorCode::VALUE_SHOULD_NOT_BE_EMPTY);
370
        $this->assertInvalidValueExceptionPropertyPath(0, 'value');
371
        $this->createHydrator()->hydrate(ObjectWithString::class, $data);
372
    }
373
374
    /**
375
     * @group string
376
     * @dataProvider notStringDataProvider
377
     */
378
    public function testHydrateStringPropertyWithNotString(array $data): void
379
    {
380
        $this->assertInvalidValueExceptionCount(1);
381
        $this->assertInvalidValueExceptionMessage(0, 'This value should be of type string.');
382
        $this->assertInvalidValueExceptionErrorCode(0, ErrorCode::VALUE_SHOULD_BE_STRING);
383
        $this->assertInvalidValueExceptionPropertyPath(0, 'value');
384
        $this->createHydrator()->hydrate(ObjectWithString::class, $data);
385
    }
386
387
    /**
388
     * @group string
389
     */
390
    public function testHydrateRequiredStringPropertyWithoutValue(): void
391
    {
392
        $this->assertInvalidValueExceptionCount(1);
393
        $this->assertInvalidValueExceptionMessage(0, 'This value should be provided.');
394
        $this->assertInvalidValueExceptionErrorCode(0, ErrorCode::VALUE_SHOULD_BE_PROVIDED);
395
        $this->assertInvalidValueExceptionPropertyPath(0, 'value');
396
        $this->createHydrator()->hydrate(ObjectWithString::class, []);
397
    }
398
399
    /**
400
     * @group array
401
     * @dataProvider arrayDataProvider
402
     */
403
    public function testHydrateArrayProperty(array $data, array $expected): void
404
    {
405
        $this->assertInvalidValueExceptionCount(0);
406
        $object = $this->createHydrator()->hydrate(ObjectWithArray::class, $data);
407
        $this->assertSame($expected, $object->value);
408
    }
409
410
    /**
411
     * @group array
412
     * @dataProvider arrayDataProvider
413
     * @dataProvider strictNullDataProvider
414
     */
415
    public function testHydrateNullableArrayProperty(array $data, ?array $expected): void
416
    {
417
        $this->assertInvalidValueExceptionCount(0);
418
        $object = $this->createHydrator()->hydrate(ObjectWithNullableArray::class, $data);
419
        $this->assertSame($expected, $object->value);
420
    }
421
422
    /**
423
     * @group array
424
     * @dataProvider arrayDataProvider
425
     * @dataProvider emptyDataProvider
426
     */
427
    public function testHydrateOptionalArrayProperty(array $data, array $expected = []): void
428
    {
429
        $this->assertInvalidValueExceptionCount(0);
430
        $object = $this->createHydrator()->hydrate(ObjectWithOptionalArray::class, $data);
431
        $this->assertSame($expected, $object->value);
432
    }
433
434
    /**
435
     * @group array
436
     * @dataProvider strictNullDataProvider
437
     */
438
    public function testHydrateNonNullableArrayPropertyWithNull(array $data): void
439
    {
440
        $this->assertInvalidValueExceptionCount(1);
441
        $this->assertInvalidValueExceptionMessage(0, 'This value should not be empty.');
442
        $this->assertInvalidValueExceptionErrorCode(0, ErrorCode::VALUE_SHOULD_NOT_BE_EMPTY);
443
        $this->assertInvalidValueExceptionPropertyPath(0, 'value');
444
        $this->createHydrator()->hydrate(ObjectWithArray::class, $data);
445
    }
446
447
    /**
448
     * @group array
449
     * @dataProvider notArrayDataProvider
450
     */
451
    public function testHydrateArrayPropertyWithNotArray(array $data): void
452
    {
453
        $this->assertInvalidValueExceptionCount(1);
454
        $this->assertInvalidValueExceptionMessage(0, 'This value should be of type array.');
455
        $this->assertInvalidValueExceptionErrorCode(0, ErrorCode::VALUE_SHOULD_BE_ARRAY);
456
        $this->assertInvalidValueExceptionPropertyPath(0, 'value');
457
        $this->createHydrator()->hydrate(ObjectWithArray::class, $data);
458
    }
459
460
    /**
461
     * @group array
462
     */
463
    public function testHydrateRequiredArrayPropertyWithoutValue(): void
464
    {
465
        $this->assertInvalidValueExceptionCount(1);
466
        $this->assertInvalidValueExceptionMessage(0, 'This value should be provided.');
467
        $this->assertInvalidValueExceptionErrorCode(0, ErrorCode::VALUE_SHOULD_BE_PROVIDED);
468
        $this->assertInvalidValueExceptionPropertyPath(0, 'value');
469
        $this->createHydrator()->hydrate(ObjectWithArray::class, []);
470
    }
471
472
    /**
473
     * @group collection
474
     * @dataProvider arrayDataProvider
475
     */
476
    public function testHydrateCollectionProperty(array $data, array $expected): void
477
    {
478
        $this->assertInvalidValueExceptionCount(0);
479
        $object = $this->createHydrator()->hydrate(ObjectWithCollection::class, $data);
480
        $this->assertSame($expected, $object->value->elements);
481
    }
482
483
    /**
484
     * @group collection
485
     * @dataProvider arrayDataProvider
486
     * @dataProvider strictNullDataProvider
487
     */
488
    public function testHydrateNullableCollectionProperty(array $data, ?array $expected): void
489
    {
490
        $this->assertInvalidValueExceptionCount(0);
491
        $object = $this->createHydrator()->hydrate(ObjectWithNullableCollection::class, $data);
492
        $this->assertSame($expected, isset($object->value) ? $object->value->elements : null);
493
    }
494
495
    /**
496
     * @group collection
497
     * @dataProvider arrayDataProvider
498
     * @dataProvider emptyDataProvider
499
     */
500
    public function testHydrateOptionalCollectionProperty(array $data, array $expected = []): void
501
    {
502
        $this->assertInvalidValueExceptionCount(0);
503
        $object = $this->createHydrator()->hydrate(ObjectWithOptionalCollection::class, $data);
504
        $this->assertSame($expected, isset($object->value) ? $object->value->elements : []);
505
    }
506
507
    /**
508
     * @group collection
509
     * @dataProvider strictNullDataProvider
510
     */
511
    public function testHydrateNonNullableCollectionPropertyWithNull(array $data): void
512
    {
513
        $this->assertInvalidValueExceptionCount(1);
514
        $this->assertInvalidValueExceptionMessage(0, 'This value should not be empty.');
515
        $this->assertInvalidValueExceptionErrorCode(0, ErrorCode::VALUE_SHOULD_NOT_BE_EMPTY);
516
        $this->assertInvalidValueExceptionPropertyPath(0, 'value');
517
        $this->createHydrator()->hydrate(ObjectWithCollection::class, $data);
518
    }
519
520
    /**
521
     * @group collection
522
     * @dataProvider notArrayDataProvider
523
     */
524
    public function testHydrateCollectionPropertyWithNotArray(array $data): void
525
    {
526
        $this->assertInvalidValueExceptionCount(1);
527
        $this->assertInvalidValueExceptionMessage(0, 'This value should be of type array.');
528
        $this->assertInvalidValueExceptionErrorCode(0, ErrorCode::VALUE_SHOULD_BE_ARRAY);
529
        $this->assertInvalidValueExceptionPropertyPath(0, 'value');
530
        $this->createHydrator()->hydrate(ObjectWithCollection::class, $data);
531
    }
532
533
    /**
534
     * @group collection
535
     */
536
    public function testHydrateRequiredCollectionPropertyWithoutValue(): void
537
    {
538
        $this->assertInvalidValueExceptionCount(1);
539
        $this->assertInvalidValueExceptionMessage(0, 'This value should be provided.');
540
        $this->assertInvalidValueExceptionErrorCode(0, ErrorCode::VALUE_SHOULD_BE_PROVIDED);
541
        $this->assertInvalidValueExceptionPropertyPath(0, 'value');
542
        $this->createHydrator()->hydrate(ObjectWithCollection::class, []);
543
    }
544
545
    /**
546
     * @group collection
547
     */
548
    public function testOverflowCollection(): void
549
    {
550
        $this->assertInvalidValueExceptionCount(1);
551
        $this->assertInvalidValueExceptionMessage(0, 'The maximum allowed number of elements is 1.');
552
        $this->assertInvalidValueExceptionErrorCode(0, ErrorCode::REDUNDANT_ELEMENT);
553
        $this->assertInvalidValueExceptionPropertyPath(0, 'value.1');
554
        $this->createHydrator()->hydrate(ObjectWithCollection::class, ['value' => [
555
            ['value' => 'foo'],
556
            ['value' => 'bar'],
557
        ]]);
558
    }
559
560
    /**
561
     * @group datetimeTimestamp
562
     * @dataProvider timestampDataProvider
563
     */
564
    public function testHydrateTimestampProperty(array $data, string $expected, string $format): void
565
    {
566
        $this->assertInvalidValueExceptionCount(0);
567
        $object = $this->createHydrator()->hydrate(ObjectWithTimestamp::class, $data);
568
        $this->assertSame($expected, $object->value->format($format));
569
    }
570
571
    /**
572
     * @group datetimeTimestamp
573
     * @dataProvider timestampDataProvider
574
     * @dataProvider strictNullDataProvider
575
     * @dataProvider nonStrictNullDataProvider
576
     */
577
    // phpcs:ignore Generic.Files.LineLength
578
    public function testHydrateNullableTimestampProperty(array $data, ?string $expected = null, ?string $format = null): void
579
    {
580
        $this->assertInvalidValueExceptionCount(0);
581
        $object = $this->createHydrator()->hydrate(ObjectWithNullableTimestamp::class, $data);
582
        $this->assertSame($expected, isset($object->value, $format) ? $object->value->format($format) : null);
583
    }
584
585
    /**
586
     * @group datetimeTimestamp
587
     * @dataProvider timestampDataProvider
588
     * @dataProvider emptyDataProvider
589
     */
590
    // phpcs:ignore Generic.Files.LineLength
591
    public function testHydrateOptionalTimestampProperty(array $data, ?string $expected = null, ?string $format = null): void
592
    {
593
        $this->assertInvalidValueExceptionCount(0);
594
        $object = $this->createHydrator()->hydrate(ObjectWithOptionalTimestamp::class, $data);
595
        $this->assertSame($expected, isset($object->value, $format) ? $object->value->format($format) : null);
596
    }
597
598
    /**
599
     * @group datetimeTimestamp
600
     * @dataProvider strictNullDataProvider
601
     * @dataProvider nonStrictNullDataProvider
602
     */
603
    public function testHydrateNonNullableTimestampPropertyWithNull(array $data): void
604
    {
605
        $this->assertInvalidValueExceptionCount(1);
606
        $this->assertInvalidValueExceptionMessage(0, 'This value should not be empty.');
607
        $this->assertInvalidValueExceptionErrorCode(0, ErrorCode::VALUE_SHOULD_NOT_BE_EMPTY);
608
        $this->assertInvalidValueExceptionPropertyPath(0, 'value');
609
        $this->createHydrator()->hydrate(ObjectWithTimestamp::class, $data);
610
    }
611
612
    /**
613
     * @group datetimeTimestamp
614
     * @dataProvider notStringDataProvider
615
     */
616
    public function testHydrateTimestampPropertyWithNotString(array $data): void
617
    {
618
        $this->assertInvalidValueExceptionCount(1);
619
        $this->assertInvalidValueExceptionMessage(0, 'This value should be of type string.');
620
        $this->assertInvalidValueExceptionErrorCode(0, ErrorCode::VALUE_SHOULD_BE_STRING);
621
        $this->assertInvalidValueExceptionPropertyPath(0, 'value');
622
        $this->createHydrator()->hydrate(ObjectWithTimestamp::class, $data);
623
    }
624
625
    /**
626
     * @group datetimeTimestamp
627
     * @dataProvider invalidTimestampDataProvider
628
     */
629
    public function testHydrateTimestampPropertyWithInvalidTimestamp(array $data): void
630
    {
631
        $this->assertInvalidValueExceptionCount(1);
632
        $message = sprintf('This value should be in the format "%s".', ObjectWithTimestamp::FORMAT);
633
        $this->assertInvalidValueExceptionMessage(0, $message);
634
        $this->assertInvalidValueExceptionErrorCode(0, ErrorCode::INVALID_TIMESTAMP);
635
        $this->assertInvalidValueExceptionPropertyPath(0, 'value');
636
        $this->createHydrator()->hydrate(ObjectWithTimestamp::class, $data);
637
    }
638
639
    /**
640
     * @group datetimeTimestamp
641
     */
642
    public function testHydrateRequiredTimestampPropertyWithoutValue(): void
643
    {
644
        $this->assertInvalidValueExceptionCount(1);
645
        $this->assertInvalidValueExceptionMessage(0, 'This value should be provided.');
646
        $this->assertInvalidValueExceptionErrorCode(0, ErrorCode::VALUE_SHOULD_BE_PROVIDED);
647
        $this->assertInvalidValueExceptionPropertyPath(0, 'value');
648
        $this->createHydrator()->hydrate(ObjectWithTimestamp::class, []);
649
    }
650
651
    /**
652
     * @group datetimeTimestamp
653
     * @dataProvider timestampDataProvider
654
     */
655
    public function testHydrateUnformattedTimestampProperty(array $data): void
656
    {
657
        $this->expectException(UnsupportedPropertyTypeException::class);
658
        $this->expectExceptionMessage(sprintf(
659
            'The property %1$s.%2$s must contain the attribute %3$s, ' .
660
            'for example: #[\%3$s(\DateTimeInterface::DATE_RFC3339)].',
661
            ObjectWithUnformattedTimestampProperty::class,
662
            'value',
663
            Format::class,
664
        ));
665
666
        $this->createHydrator()->hydrate(ObjectWithUnformattedTimestampProperty::class, $data);
667
    }
668
669
    /**
670
     * @group unixTimestamp
671
     * @dataProvider strictUnixTimeStampDataProvider
672
     * @dataProvider nonStrictUnixTimeStampDataProvider
673
     */
674
    public function testHydrateUnixTimeStampProperty(array $data, string $expected, string $format): void
675
    {
676
        $this->assertInvalidValueExceptionCount(0);
677
        $object = $this->createHydrator()->hydrate(ObjectWithUnixTimeStamp::class, $data);
678
        $this->assertSame($expected, $object->value->format($format));
679
    }
680
681
    /**
682
     * @group unixTimestamp
683
     * @dataProvider strictUnixTimeStampDataProvider
684
     * @dataProvider nonStrictUnixTimeStampDataProvider
685
     * @dataProvider strictNullDataProvider
686
     * @dataProvider nonStrictNullDataProvider
687
     */
688
    // phpcs:ignore Generic.Files.LineLength
689
    public function testHydrateNullableUnixTimeStampProperty(array $data, ?string $expected = null, ?string $format = null): void
690
    {
691
        $this->assertInvalidValueExceptionCount(0);
692
        $object = $this->createHydrator()->hydrate(ObjectWithNullableUnixTimeStamp::class, $data);
693
        $this->assertSame($expected, isset($object->value, $format) ? $object->value->format($format) : null);
694
    }
695
696
    /**
697
     * @group unixTimestamp
698
     * @dataProvider strictUnixTimeStampDataProvider
699
     * @dataProvider nonStrictUnixTimeStampDataProvider
700
     * @dataProvider emptyDataProvider
701
     */
702
    // phpcs:ignore Generic.Files.LineLength
703
    public function testHydrateOptionalUnixTimeStampProperty(array $data, ?string $expected = null, ?string $format = null): void
704
    {
705
        $this->assertInvalidValueExceptionCount(0);
706
        $object = $this->createHydrator()->hydrate(ObjectWithOptionalUnixTimeStamp::class, $data);
707
        $this->assertSame($expected, isset($object->value, $format) ? $object->value->format($format) : null);
708
    }
709
710
    /**
711
     * @group unixTimestamp
712
     * @dataProvider strictNullDataProvider
713
     * @dataProvider nonStrictNullDataProvider
714
     */
715
    public function testHydrateNonNullableUnixTimeStampPropertyWithNull(array $data): void
716
    {
717
        $this->assertInvalidValueExceptionCount(1);
718
        $this->assertInvalidValueExceptionMessage(0, 'This value should not be empty.');
719
        $this->assertInvalidValueExceptionErrorCode(0, ErrorCode::VALUE_SHOULD_NOT_BE_EMPTY);
720
        $this->assertInvalidValueExceptionPropertyPath(0, 'value');
721
        $this->createHydrator()->hydrate(ObjectWithUnixTimeStamp::class, $data);
722
    }
723
724
    /**
725
     * @group unixTimestamp
726
     * @dataProvider notIntegerDataProvider
727
     */
728
    public function testHydrateUnixTimeStampPropertyWithNotInteger(array $data): void
729
    {
730
        $this->assertInvalidValueExceptionCount(1);
731
        $this->assertInvalidValueExceptionMessage(0, 'This value should be of type integer.');
732
        $this->assertInvalidValueExceptionErrorCode(0, ErrorCode::VALUE_SHOULD_BE_INTEGER);
733
        $this->assertInvalidValueExceptionPropertyPath(0, 'value');
734
        $this->createHydrator()->hydrate(ObjectWithUnixTimeStamp::class, $data);
735
    }
736
737
    /**
738
     * @group unixTimestamp
739
     */
740
    public function testHydrateRequiredUnixTimeStampPropertyWithoutValue(): void
741
    {
742
        $this->assertInvalidValueExceptionCount(1);
743
        $this->assertInvalidValueExceptionMessage(0, 'This value should be provided.');
744
        $this->assertInvalidValueExceptionErrorCode(0, ErrorCode::VALUE_SHOULD_BE_PROVIDED);
745
        $this->assertInvalidValueExceptionPropertyPath(0, 'value');
746
        $this->createHydrator()->hydrate(ObjectWithUnixTimeStamp::class, []);
747
    }
748
749
    /**
750
     * @group timezone
751
     * @dataProvider timezoneDataProvider
752
     */
753
    public function testHydrateTimezoneProperty(array $data, string $expected): void
754
    {
755
        $this->assertInvalidValueExceptionCount(0);
756
        $object = $this->createHydrator()->hydrate(ObjectWithTimezone::class, $data);
757
        $this->assertSame($expected, $object->value->getName());
758
    }
759
760
    /**
761
     * @group timezone
762
     * @dataProvider timezoneDataProvider
763
     * @dataProvider strictNullDataProvider
764
     * @dataProvider nonStrictNullDataProvider
765
     */
766
    public function testHydrateNullableTimezoneProperty(array $data, ?string $expected = null): void
767
    {
768
        $this->assertInvalidValueExceptionCount(0);
769
        $object = $this->createHydrator()->hydrate(ObjectWithNullableTimezone::class, $data);
770
        $this->assertSame($expected, isset($object->value) ? $object->value->getName() : null);
771
    }
772
773
    /**
774
     * @group timezone
775
     * @dataProvider timezoneDataProvider
776
     * @dataProvider emptyDataProvider
777
     */
778
    public function testHydrateOptionalTimezoneProperty(array $data, ?string $expected = null): void
779
    {
780
        $this->assertInvalidValueExceptionCount(0);
781
        $object = $this->createHydrator()->hydrate(ObjectWithOptionalTimezone::class, $data);
782
        $this->assertSame($expected, isset($object->value) ? $object->value->getName() : null);
783
    }
784
785
    /**
786
     * @group timezone
787
     * @dataProvider strictNullDataProvider
788
     * @dataProvider nonStrictNullDataProvider
789
     */
790
    public function testHydrateNonNullableTimezonePropertyWithNull(array $data): void
791
    {
792
        $this->assertInvalidValueExceptionCount(1);
793
        $this->assertInvalidValueExceptionMessage(0, 'This value should not be empty.');
794
        $this->assertInvalidValueExceptionErrorCode(0, ErrorCode::VALUE_SHOULD_NOT_BE_EMPTY);
795
        $this->assertInvalidValueExceptionPropertyPath(0, 'value');
796
        $this->createHydrator()->hydrate(ObjectWithTimezone::class, $data);
797
    }
798
799
    /**
800
     * @group timezone
801
     * @dataProvider notStringDataProvider
802
     */
803
    public function testHydrateTimezonePropertyWithNotString(array $data): void
804
    {
805
        $this->assertInvalidValueExceptionCount(1);
806
        $this->assertInvalidValueExceptionMessage(0, 'This value should be of type string.');
807
        $this->assertInvalidValueExceptionErrorCode(0, ErrorCode::VALUE_SHOULD_BE_STRING);
808
        $this->assertInvalidValueExceptionPropertyPath(0, 'value');
809
        $this->createHydrator()->hydrate(ObjectWithTimezone::class, $data);
810
    }
811
812
    /**
813
     * @group timezone
814
     * @dataProvider invalidTimezoneDataProvider
815
     */
816
    public function testHydrateTimezonePropertyWithInvalidTimezone(array $data): void
817
    {
818
        $this->assertInvalidValueExceptionCount(1);
819
        $this->assertInvalidValueExceptionMessage(0, 'This value is not a valid timezone.');
820
        $this->assertInvalidValueExceptionErrorCode(0, ErrorCode::INVALID_TIMEZONE);
821
        $this->assertInvalidValueExceptionPropertyPath(0, 'value');
822
        $this->createHydrator()->hydrate(ObjectWithTimezone::class, $data);
823
    }
824
825
    /**
826
     * @group timezone
827
     */
828
    public function testHydrateRequiredTimezonePropertyWithoutValue(): void
829
    {
830
        $this->assertInvalidValueExceptionCount(1);
831
        $this->assertInvalidValueExceptionMessage(0, 'This value should be provided.');
832
        $this->assertInvalidValueExceptionErrorCode(0, ErrorCode::VALUE_SHOULD_BE_PROVIDED);
833
        $this->assertInvalidValueExceptionPropertyPath(0, 'value');
834
        $this->createHydrator()->hydrate(ObjectWithTimezone::class, []);
835
    }
836
837
    /**
838
     * @group uid
839
     * @dataProvider uidDataProvider
840
     */
841
    public function testHydrateUidProperty(array $data, string $expected): void
842
    {
843
        $this->assertInvalidValueExceptionCount(0);
844
        $object = $this->createHydrator()->hydrate(ObjectWithUid::class, $data);
845
        $this->assertSame($expected, $object->value->toRfc4122());
846
    }
847
848
    /**
849
     * @group uid
850
     * @dataProvider uidDataProvider
851
     * @dataProvider strictNullDataProvider
852
     * @dataProvider nonStrictNullDataProvider
853
     */
854
    public function testHydrateNullableUidProperty(array $data, ?string $expected = null): void
855
    {
856
        $this->assertInvalidValueExceptionCount(0);
857
        $object = $this->createHydrator()->hydrate(ObjectWithNullableUid::class, $data);
858
        $this->assertSame($expected, isset($object->value) ? $object->value->toRfc4122() : null);
859
    }
860
861
    /**
862
     * @group uid
863
     * @dataProvider uidDataProvider
864
     * @dataProvider emptyDataProvider
865
     */
866
    public function testHydrateOptionalUidProperty(array $data, ?string $expected = null): void
867
    {
868
        $this->assertInvalidValueExceptionCount(0);
869
        $object = $this->createHydrator()->hydrate(ObjectWithOptionalUid::class, $data);
870
        $this->assertSame($expected, isset($object->value) ? $object->value->toRfc4122() : null);
871
    }
872
873
    /**
874
     * @group uid
875
     * @dataProvider strictNullDataProvider
876
     * @dataProvider nonStrictNullDataProvider
877
     */
878
    public function testHydrateNonNullableUidPropertyWithNull(array $data): void
879
    {
880
        $this->assertInvalidValueExceptionCount(1);
881
        $this->assertInvalidValueExceptionMessage(0, 'This value should not be empty.');
882
        $this->assertInvalidValueExceptionErrorCode(0, ErrorCode::VALUE_SHOULD_NOT_BE_EMPTY);
883
        $this->assertInvalidValueExceptionPropertyPath(0, 'value');
884
        $this->createHydrator()->hydrate(ObjectWithUid::class, $data);
885
    }
886
887
    /**
888
     * @group uid
889
     * @dataProvider notStringDataProvider
890
     */
891
    public function testHydrateUidPropertyWithNotString(array $data): void
892
    {
893
        $this->assertInvalidValueExceptionCount(1);
894
        $this->assertInvalidValueExceptionMessage(0, 'This value should be of type string.');
895
        $this->assertInvalidValueExceptionErrorCode(0, ErrorCode::VALUE_SHOULD_BE_STRING);
896
        $this->assertInvalidValueExceptionPropertyPath(0, 'value');
897
        $this->createHydrator()->hydrate(ObjectWithUid::class, $data);
898
    }
899
900
    /**
901
     * @group uid
902
     * @dataProvider invalidUidDataProvider
903
     */
904
    public function testHydrateUidPropertyWithInvalidUid(array $data): void
905
    {
906
        $this->assertInvalidValueExceptionCount(1);
907
        $this->assertInvalidValueExceptionMessage(0, 'This value is not a valid UID.');
908
        $this->assertInvalidValueExceptionErrorCode(0, ErrorCode::INVALID_UID);
909
        $this->assertInvalidValueExceptionPropertyPath(0, 'value');
910
        $this->createHydrator()->hydrate(ObjectWithUid::class, $data);
911
    }
912
913
    /**
914
     * @group uid
915
     */
916
    public function testHydrateRequiredUidPropertyWithoutValue(): void
917
    {
918
        $this->assertInvalidValueExceptionCount(1);
919
        $this->assertInvalidValueExceptionMessage(0, 'This value should be provided.');
920
        $this->assertInvalidValueExceptionErrorCode(0, ErrorCode::VALUE_SHOULD_BE_PROVIDED);
921
        $this->assertInvalidValueExceptionPropertyPath(0, 'value');
922
        $this->createHydrator()->hydrate(ObjectWithUid::class, []);
923
    }
924
925
    /**
926
     * @group integerEnumeration
927
     * @dataProvider strictIntegerEnumerationDataProvider
928
     * @dataProvider nonStrictIntegerEnumerationDataProvider
929
     * @param IntegerEnum $expected
930
     */
931
    public function testHydrateIntegerEnumerationProperty(array $data, $expected): void
932
    {
933
        $this->phpRequired('8.1');
934
        $this->assertInvalidValueExceptionCount(0);
935
        $object = $this->createHydrator()->hydrate(ObjectWithIntegerEnum::class, $data);
936
        $this->assertSame($expected, $object->value);
937
    }
938
939
    /**
940
     * @group integerEnumeration
941
     * @dataProvider strictIntegerEnumerationDataProvider
942
     * @dataProvider nonStrictIntegerEnumerationDataProvider
943
     * @dataProvider strictNullDataProvider
944
     * @dataProvider nonStrictNullDataProvider
945
     * @param IntegerEnum|null $expected
946
     */
947
    public function testHydrateNullableIntegerEnumerationProperty(array $data, $expected): void
948
    {
949
        $this->phpRequired('8.1');
950
        $this->assertInvalidValueExceptionCount(0);
951
        $object = $this->createHydrator()->hydrate(ObjectWithNullableIntegerEnum::class, $data);
952
        $this->assertSame($expected, $object->value);
953
    }
954
955
    /**
956
     * @group integerEnumeration
957
     * @dataProvider strictIntegerEnumerationDataProvider
958
     * @dataProvider nonStrictIntegerEnumerationDataProvider
959
     * @dataProvider emptyDataProvider
960
     * @param IntegerEnum|null $expected
961
     */
962
    // phpcs:ignore Generic.Files.LineLength
963
    public function testHydrateOptionalIntegerEnumerationProperty(array $data, $expected = null): void
964
    {
965
        $this->phpRequired('8.1');
966
        $this->assertInvalidValueExceptionCount(0);
967
        $object = $this->createHydrator()->hydrate(ObjectWithOptionalIntegerEnum::class, $data);
968
        $this->assertSame($expected ?? IntegerEnum::FOO, $object->value);
969
    }
970
971
    /**
972
     * @group integerEnumeration
973
     * @dataProvider strictNullDataProvider
974
     * @dataProvider nonStrictNullDataProvider
975
     */
976
    public function testHydrateNonNullableIntegerEnumerationPropertyWithNull(array $data): void
977
    {
978
        $this->phpRequired('8.1');
979
        $this->assertInvalidValueExceptionCount(1);
980
        $this->assertInvalidValueExceptionMessage(0, 'This value should not be empty.');
981
        $this->assertInvalidValueExceptionErrorCode(0, ErrorCode::VALUE_SHOULD_NOT_BE_EMPTY);
982
        $this->assertInvalidValueExceptionPropertyPath(0, 'value');
983
        $this->createHydrator()->hydrate(ObjectWithIntegerEnum::class, $data);
984
    }
985
986
    /**
987
     * @group integerEnumeration
988
     * @dataProvider notIntegerDataProvider
989
     */
990
    public function testHydrateIntegerEnumerationPropertyWithNotInteger(array $data): void
991
    {
992
        $this->phpRequired('8.1');
993
        $this->assertInvalidValueExceptionCount(1);
994
        $this->assertInvalidValueExceptionMessage(0, 'This value should be of type integer.');
995
        $this->assertInvalidValueExceptionErrorCode(0, ErrorCode::VALUE_SHOULD_BE_INTEGER);
996
        $this->assertInvalidValueExceptionPropertyPath(0, 'value');
997
        $this->createHydrator()->hydrate(ObjectWithIntegerEnum::class, $data);
998
    }
999
1000
    /**
1001
     * @group integerEnumeration
1002
     */
1003
    public function testHydrateRequiredIntegerEnumerationPropertyWithoutValue(): void
1004
    {
1005
        $this->assertInvalidValueExceptionCount(1);
1006
        $this->assertInvalidValueExceptionMessage(0, 'This value should be provided.');
1007
        $this->assertInvalidValueExceptionErrorCode(0, ErrorCode::VALUE_SHOULD_BE_PROVIDED);
1008
        $this->assertInvalidValueExceptionPropertyPath(0, 'value');
1009
        $this->createHydrator()->hydrate(ObjectWithIntegerEnum::class, []);
1010
    }
1011
1012
    /**
1013
     * @group stringEnumeration
1014
     */
1015
    public function testHydrateIntegerEnumerationPropertyWithInvalidChoice(): void
1016
    {
1017
        $this->phpRequired('8.1');
1018
        $this->assertInvalidValueExceptionCount(1);
1019
        // phpcs:ignore Generic.Files.LineLength
1020
        $this->assertInvalidValueExceptionMessage(0, 'This value should be one of: 1, 2, 3.');
1021
        $this->assertInvalidValueExceptionErrorCode(0, ErrorCode::INVALID_CHOICE);
1022
        $this->assertInvalidValueExceptionPropertyPath(0, 'value');
1023
        $this->createHydrator()->hydrate(ObjectWithIntegerEnum::class, ['value' => 42]);
1024
    }
1025
1026
    /**
1027
     * @group stringEnumeration
1028
     * @dataProvider stringEnumerationDataProvider
1029
     * @param StringEnum $expected
1030
     */
1031
    public function testHydrateStringEnumerationProperty(array $data, $expected): void
1032
    {
1033
        $this->phpRequired('8.1');
1034
        $this->assertInvalidValueExceptionCount(0);
1035
        $object = $this->createHydrator()->hydrate(ObjectWithStringEnum::class, $data);
1036
        $this->assertSame($expected, $object->value);
1037
    }
1038
1039
    /**
1040
     * @group stringEnumeration
1041
     * @dataProvider stringEnumerationDataProvider
1042
     * @dataProvider strictNullDataProvider
1043
     * @dataProvider nonStrictNullDataProvider
1044
     * @param StringEnum|null $expected
1045
     */
1046
    public function testHydrateNullableStringEnumerationProperty(array $data, $expected): void
1047
    {
1048
        $this->phpRequired('8.1');
1049
        $this->assertInvalidValueExceptionCount(0);
1050
        $object = $this->createHydrator()->hydrate(ObjectWithNullableStringEnum::class, $data);
1051
        $this->assertSame($expected, $object->value);
1052
    }
1053
1054
    /**
1055
     * @group stringEnumeration
1056
     * @dataProvider stringEnumerationDataProvider
1057
     * @dataProvider emptyDataProvider
1058
     * @param StringEnum|null $expected
1059
     */
1060
    // phpcs:ignore Generic.Files.LineLength
1061
    public function testHydrateOptionalStringEnumerationProperty(array $data, $expected = null): void
1062
    {
1063
        $this->phpRequired('8.1');
1064
        $this->assertInvalidValueExceptionCount(0);
1065
        $object = $this->createHydrator()->hydrate(ObjectWithOptionalStringEnum::class, $data);
1066
        $this->assertSame($expected ?? StringEnum::FOO, $object->value);
1067
    }
1068
1069
    /**
1070
     * @group stringEnumeration
1071
     * @dataProvider strictNullDataProvider
1072
     * @dataProvider nonStrictNullDataProvider
1073
     */
1074
    public function testHydrateNonNullableStringEnumerationPropertyWithNull(array $data): void
1075
    {
1076
        $this->phpRequired('8.1');
1077
        $this->assertInvalidValueExceptionCount(1);
1078
        $this->assertInvalidValueExceptionMessage(0, 'This value should not be empty.');
1079
        $this->assertInvalidValueExceptionErrorCode(0, ErrorCode::VALUE_SHOULD_NOT_BE_EMPTY);
1080
        $this->assertInvalidValueExceptionPropertyPath(0, 'value');
1081
        $this->createHydrator()->hydrate(ObjectWithStringEnum::class, $data);
1082
    }
1083
1084
    /**
1085
     * @group stringEnumeration
1086
     * @dataProvider notStringDataProvider
1087
     */
1088
    public function testHydrateStringEnumerationPropertyWithNotInteger(array $data): void
1089
    {
1090
        $this->phpRequired('8.1');
1091
        $this->assertInvalidValueExceptionCount(1);
1092
        $this->assertInvalidValueExceptionMessage(0, 'This value should be of type string.');
1093
        $this->assertInvalidValueExceptionErrorCode(0, ErrorCode::VALUE_SHOULD_BE_STRING);
1094
        $this->assertInvalidValueExceptionPropertyPath(0, 'value');
1095
        $this->createHydrator()->hydrate(ObjectWithStringEnum::class, $data);
1096
    }
1097
1098
    /**
1099
     * @group stringEnumeration
1100
     */
1101
    public function testHydrateStringEnumerationPropertyWithInvalidChoice(): void
1102
    {
1103
        $this->phpRequired('8.1');
1104
        $this->assertInvalidValueExceptionCount(1);
1105
        // phpcs:ignore Generic.Files.LineLength
1106
        $this->assertInvalidValueExceptionMessage(0, 'This value should be one of: foo, bar, baz.');
1107
        $this->assertInvalidValueExceptionErrorCode(0, ErrorCode::INVALID_CHOICE);
1108
        $this->assertInvalidValueExceptionPropertyPath(0, 'value');
1109
        $this->createHydrator()->hydrate(ObjectWithStringEnum::class, ['value' => 'unknown']);
1110
    }
1111
1112
    /**
1113
     * @group stringEnumeration
1114
     */
1115
    public function testHydrateRequiredStringEnumerationPropertyWithoutValue(): void
1116
    {
1117
        $this->assertInvalidValueExceptionCount(1);
1118
        $this->assertInvalidValueExceptionMessage(0, 'This value should be provided.');
1119
        $this->assertInvalidValueExceptionErrorCode(0, ErrorCode::VALUE_SHOULD_BE_PROVIDED);
1120
        $this->assertInvalidValueExceptionPropertyPath(0, 'value');
1121
        $this->createHydrator()->hydrate(ObjectWithStringEnum::class, []);
1122
    }
1123
1124
    /**
1125
     * @group relationship
1126
     * @dataProvider stringDataProvider
1127
     */
1128
    public function testHydrateRelationshipProperty(array $data, string $expected): void
1129
    {
1130
        $this->assertInvalidValueExceptionCount(0);
1131
        $object = $this->createHydrator()->hydrate(ObjectWithRelationship::class, ['value' => $data]);
1132
        $this->assertSame($expected, $object->value->value);
1133
    }
1134
1135
    /**
1136
     * @group relationship
1137
     * @dataProvider strictNullDataProvider
1138
     */
1139
    public function testHydrateNullableRelationshipProperty(array $data): void
1140
    {
1141
        $this->assertInvalidValueExceptionCount(0);
1142
        $object = $this->createHydrator()->hydrate(ObjectWithNullableRelationship::class, $data);
1143
        $this->assertNull($object->value);
1144
    }
1145
1146
    /**
1147
     * @group relationship
1148
     * @dataProvider emptyDataProvider
1149
     */
1150
    public function testHydrateOptionalRelationshipProperty(array $data): void
1151
    {
1152
        $this->assertInvalidValueExceptionCount(0);
1153
        $object = $this->createHydrator()->hydrate(ObjectWithOptionalRelationship::class, $data);
1154
        $this->assertNull($object->value);
1155
    }
1156
1157
    /**
1158
     * @group relationship
1159
     * @dataProvider strictNullDataProvider
1160
     */
1161
    public function testHydrateNonNullableRelationshipPropertyWithNull(array $data): void
1162
    {
1163
        $this->assertInvalidValueExceptionCount(1);
1164
        $this->assertInvalidValueExceptionMessage(0, 'This value should not be empty.');
1165
        $this->assertInvalidValueExceptionErrorCode(0, ErrorCode::VALUE_SHOULD_NOT_BE_EMPTY);
1166
        $this->assertInvalidValueExceptionPropertyPath(0, 'value');
1167
        $this->createHydrator()->hydrate(ObjectWithRelationship::class, $data);
1168
    }
1169
1170
    /**
1171
     * @group relationship
1172
     * @dataProvider notArrayDataProvider
1173
     */
1174
    public function testHydrateRelationshipPropertyWithNotArray(array $data): void
1175
    {
1176
        $this->assertInvalidValueExceptionCount(1);
1177
        $this->assertInvalidValueExceptionMessage(0, 'This value should be of type array.');
1178
        $this->assertInvalidValueExceptionErrorCode(0, ErrorCode::VALUE_SHOULD_BE_ARRAY);
1179
        $this->assertInvalidValueExceptionPropertyPath(0, 'value');
1180
        $this->createHydrator()->hydrate(ObjectWithRelationship::class, $data);
1181
    }
1182
1183
    /**
1184
     * @group relationship
1185
     */
1186
    public function testHydrateRequiredRelationshipPropertyWithoutValue(): void
1187
    {
1188
        $this->assertInvalidValueExceptionCount(1);
1189
        $this->assertInvalidValueExceptionMessage(0, 'This value should be provided.');
1190
        $this->assertInvalidValueExceptionErrorCode(0, ErrorCode::VALUE_SHOULD_BE_PROVIDED);
1191
        $this->assertInvalidValueExceptionPropertyPath(0, 'value');
1192
        $this->createHydrator()->hydrate(ObjectWithRelationship::class, []);
1193
    }
1194
1195
    /**
1196
     * @group relationship
1197
     * @dataProvider stringDataProvider
1198
     */
1199
    public function testHydrateRelationshipWithStringProperty(array $data, string $expected): void
1200
    {
1201
        $this->assertInvalidValueExceptionCount(0);
1202
        $object = $this->createHydrator()->hydrate(ObjectWithRelationshipWithString::class, ['value' => $data]);
1203
        $this->assertSame($expected, $object->value->value);
1204
    }
1205
1206
    /**
1207
     * @group relationship
1208
     * @dataProvider stringDataProvider
1209
     * @dataProvider strictNullDataProvider
1210
     */
1211
    public function testHydrateRelationshipWithNullableStringProperty(array $data, ?string $expected): void
1212
    {
1213
        $this->assertInvalidValueExceptionCount(0);
1214
        $object = $this->createHydrator()->hydrate(ObjectWithRelationshipWithNullableString::class, ['value' => $data]);
1215
        $this->assertSame($expected, $object->value->value);
1216
    }
1217
1218
    /**
1219
     * @group relationship
1220
     * @dataProvider stringDataProvider
1221
     * @dataProvider emptyDataProvider
1222
     */
1223
    public function testHydrateRelationshipWithOptionalStringProperty(array $data, string $expected = 'default'): void
1224
    {
1225
        $this->assertInvalidValueExceptionCount(0);
1226
        $object = $this->createHydrator()->hydrate(ObjectWithRelationshipWithOptionalString::class, ['value' => $data]);
1227
        $this->assertSame($expected, $object->value->value);
1228
    }
1229
1230
    /**
1231
     * @group relationship
1232
     * @dataProvider strictNullDataProvider
1233
     */
1234
    public function testHydrateRelationshipWithNonNullablePropertyWithNull(array $data): void
1235
    {
1236
        $this->assertInvalidValueExceptionCount(1);
1237
        $this->assertInvalidValueExceptionMessage(0, 'This value should not be empty.');
1238
        $this->assertInvalidValueExceptionErrorCode(0, ErrorCode::VALUE_SHOULD_NOT_BE_EMPTY);
1239
        $this->assertInvalidValueExceptionPropertyPath(0, 'value.value');
1240
        $this->createHydrator()->hydrate(ObjectWithRelationshipWithString::class, ['value' => $data]);
1241
    }
1242
1243
    /**
1244
     * @group relationship
1245
     * @dataProvider notStringDataProvider
1246
     */
1247
    public function testHydrateRelationshipWithStringPropertyWithNotString(array $data): void
1248
    {
1249
        $this->assertInvalidValueExceptionCount(1);
1250
        $this->assertInvalidValueExceptionMessage(0, 'This value should be of type string.');
1251
        $this->assertInvalidValueExceptionErrorCode(0, ErrorCode::VALUE_SHOULD_BE_STRING);
1252
        $this->assertInvalidValueExceptionPropertyPath(0, 'value.value');
1253
        $this->createHydrator()->hydrate(ObjectWithRelationshipWithString::class, ['value' => $data]);
1254
    }
1255
1256
    /**
1257
     * @group relationship
1258
     */
1259
    public function testHydrateRelationshipWithRequiredStringPropertyWithoutValue(): void
1260
    {
1261
        $this->assertInvalidValueExceptionCount(1);
1262
        $this->assertInvalidValueExceptionMessage(0, 'This value should be provided.');
1263
        $this->assertInvalidValueExceptionErrorCode(0, ErrorCode::VALUE_SHOULD_BE_PROVIDED);
1264
        $this->assertInvalidValueExceptionPropertyPath(0, 'value.value');
1265
        $this->createHydrator()->hydrate(ObjectWithRelationshipWithString::class, ['value' => []]);
1266
    }
1267
1268
    /**
1269
     * @group relationship
1270
     */
1271
    public function testHydrateUnstantiableRelationshipProperty(): void
1272
    {
1273
        $this->expectException(UnsupportedPropertyTypeException::class);
1274
        $this->expectExceptionMessage(sprintf(
1275
            'The property %s.%s refers to a non-instantiable class %s.',
1276
            ObjectWithUnstantiableRelationship::class,
1277
            'value',
1278
            UnstantiableObject::class,
1279
        ));
1280
1281
        $this->createHydrator()->hydrate(ObjectWithUnstantiableRelationship::class, ['value' => []]);
1282
    }
1283
1284
    /**
1285
     * @group relationships
1286
     */
1287
    public function testHydrateRelationshipsProperty(): void
1288
    {
1289
        $data = ['value' => [['value' => ['value' => 'foo']], ['value' => ['value' => 'bar']]]];
1290
1291
        $this->assertInvalidValueExceptionCount(0);
1292
        $object = $this->createHydrator()->hydrate(ObjectWithRelationships::class, $data);
1293
        $this->assertCount(2, $object->value);
1294
        $this->assertArrayHasKey(0, $object->value);
1295
        $this->assertSame($data['value'][0]['value']['value'], $object->value[0]->value->value);
1296
        $this->assertArrayHasKey(1, $object->value);
1297
        $this->assertSame($data['value'][1]['value']['value'], $object->value[1]->value->value);
1298
    }
1299
1300
    /**
1301
     * @group relationships
1302
     * @dataProvider strictNullDataProvider
1303
     */
1304
    public function testHydrateNullableRelationshipsProperty(array $data): void
1305
    {
1306
        $this->assertInvalidValueExceptionCount(0);
1307
        $object = $this->createHydrator()->hydrate(ObjectWithNullableRelationships::class, $data);
1308
        $this->assertNull($object->value);
1309
    }
1310
1311
    /**
1312
     * @group relationships
1313
     * @dataProvider emptyDataProvider
1314
     */
1315
    public function testHydrateOptionalRelationshipsProperty(array $data): void
1316
    {
1317
        $this->assertInvalidValueExceptionCount(0);
1318
        $object = $this->createHydrator()->hydrate(ObjectWithOptionalRelationships::class, $data);
1319
        $this->assertSame([], $object->value);
1320
    }
1321
1322
    /**
1323
     * @group relationships
1324
     * @dataProvider strictNullDataProvider
1325
     */
1326
    public function testHydrateNonNullableRelationshipsPropertyWithNull(array $data): void
1327
    {
1328
        $this->assertInvalidValueExceptionCount(1);
1329
        $this->assertInvalidValueExceptionMessage(0, 'This value should not be empty.');
1330
        $this->assertInvalidValueExceptionErrorCode(0, ErrorCode::VALUE_SHOULD_NOT_BE_EMPTY);
1331
        $this->assertInvalidValueExceptionPropertyPath(0, 'value');
1332
        $this->createHydrator()->hydrate(ObjectWithRelationships::class, $data);
1333
    }
1334
1335
    /**
1336
     * @group relationships
1337
     * @dataProvider notArrayDataProvider
1338
     */
1339
    public function testHydrateRelationshipsPropertyWithNotArray(array $data): void
1340
    {
1341
        $this->assertInvalidValueExceptionCount(1);
1342
        $this->assertInvalidValueExceptionMessage(0, 'This value should be of type array.');
1343
        $this->assertInvalidValueExceptionErrorCode(0, ErrorCode::VALUE_SHOULD_BE_ARRAY);
1344
        $this->assertInvalidValueExceptionPropertyPath(0, 'value');
1345
        $this->createHydrator()->hydrate(ObjectWithRelationships::class, $data);
1346
    }
1347
1348
    /**
1349
     * @group relationships
1350
     */
1351
    public function testHydrateRequiredRelationshipsPropertyWithoutValue(): void
1352
    {
1353
        $this->assertInvalidValueExceptionCount(1);
1354
        $this->assertInvalidValueExceptionMessage(0, 'This value should be provided.');
1355
        $this->assertInvalidValueExceptionErrorCode(0, ErrorCode::VALUE_SHOULD_BE_PROVIDED);
1356
        $this->assertInvalidValueExceptionPropertyPath(0, 'value');
1357
        $this->createHydrator()->hydrate(ObjectWithRelationships::class, []);
1358
    }
1359
1360
    /**
1361
     * @group relationships
1362
     * @dataProvider strictNullDataProvider
1363
     */
1364
    public function testHydrateRelationshipsPropertyWithNullsForNonNullableValues(array $data): void
1365
    {
1366
        $this->assertInvalidValueExceptionCount(1);
1367
        $this->assertInvalidValueExceptionMessage(0, 'This value should not be empty.');
1368
        $this->assertInvalidValueExceptionErrorCode(0, ErrorCode::VALUE_SHOULD_NOT_BE_EMPTY);
1369
        $this->assertInvalidValueExceptionPropertyPath(0, 'value.0.value.value');
1370
        $this->createHydrator()->hydrate(ObjectWithRelationships::class, ['value' => [['value' => $data]]]);
1371
    }
1372
1373
    /**
1374
     * @group relationships
1375
     * @dataProvider notStringDataProvider
1376
     */
1377
    public function testHydrateRelationshipsPropertyWithNotStringForStringValue(array $data): void
1378
    {
1379
        $this->assertInvalidValueExceptionCount(1);
1380
        $this->assertInvalidValueExceptionMessage(0, 'This value should be of type string.');
1381
        $this->assertInvalidValueExceptionErrorCode(0, ErrorCode::VALUE_SHOULD_BE_STRING);
1382
        $this->assertInvalidValueExceptionPropertyPath(0, 'value.0.value.value');
1383
        $this->createHydrator()->hydrate(ObjectWithRelationships::class, ['value' => [['value' => $data]]]);
1384
    }
1385
1386
    /**
1387
     * @group relationships
1388
     */
1389
    public function testHydrateRelationshipsPropertyWithoutValueForRequiredValue(): void
1390
    {
1391
        $this->assertInvalidValueExceptionCount(1);
1392
        $this->assertInvalidValueExceptionMessage(0, 'This value should be provided.');
1393
        $this->assertInvalidValueExceptionErrorCode(0, ErrorCode::VALUE_SHOULD_BE_PROVIDED);
1394
        $this->assertInvalidValueExceptionPropertyPath(0, 'value.0.value.value');
1395
        $this->createHydrator()->hydrate(ObjectWithRelationships::class, ['value' => [['value' => []]]]);
1396
    }
1397
1398
    /**
1399
     * @group relationships
1400
     */
1401
    public function testHydrateRelationshipsPropertyWithNotArrayForRelation(): void
1402
    {
1403
        $this->assertInvalidValueExceptionCount(1);
1404
        $this->assertInvalidValueExceptionMessage(0, 'This value should be of type array.');
1405
        $this->assertInvalidValueExceptionErrorCode(0, ErrorCode::VALUE_SHOULD_BE_ARRAY);
1406
        $this->assertInvalidValueExceptionPropertyPath(0, 'value.0');
1407
        $this->createHydrator()->hydrate(ObjectWithRelationships::class, ['value' => [null]]);
1408
    }
1409
1410
    /**
1411
     * @group relationships
1412
     */
1413
    public function testSeveralErrorsWhenHydratingRelationshipsProperty(): void
1414
    {
1415
        $this->assertInvalidValueExceptionCount(3);
1416
        $this->assertInvalidValueExceptionMessage(0, 'This value should not be empty.');
1417
        $this->assertInvalidValueExceptionErrorCode(0, ErrorCode::VALUE_SHOULD_NOT_BE_EMPTY);
1418
        $this->assertInvalidValueExceptionPropertyPath(0, 'value.0.value.value');
1419
        $this->assertInvalidValueExceptionMessage(1, 'This value should be of type string.');
1420
        $this->assertInvalidValueExceptionErrorCode(1, ErrorCode::VALUE_SHOULD_BE_STRING);
1421
        $this->assertInvalidValueExceptionPropertyPath(1, 'value.1.value.value');
1422
        $this->assertInvalidValueExceptionMessage(2, 'This value should be provided.');
1423
        $this->assertInvalidValueExceptionErrorCode(2, ErrorCode::VALUE_SHOULD_BE_PROVIDED);
1424
        $this->assertInvalidValueExceptionPropertyPath(2, 'value.2.value.value');
1425
        $this->createHydrator()->hydrate(ObjectWithRelationships::class, ['value' => [
1426
            ['value' => ['value' => null]],
1427
            ['value' => ['value' => []]],
1428
            ['value' => []]
1429
        ]]);
1430
    }
1431
1432
    /**
1433
     * @group relationships
1434
     */
1435
    public function testHydrateLimitedRelationshipsProperty(): void
1436
    {
1437
        $this->assertInvalidValueExceptionCount(1);
1438
        $this->assertInvalidValueExceptionMessage(0, 'The maximum allowed number of elements is 1.');
1439
        $this->assertInvalidValueExceptionErrorCode(0, ErrorCode::REDUNDANT_ELEMENT);
1440
        $this->assertInvalidValueExceptionPropertyPath(0, 'value.1');
1441
        $this->createHydrator()->hydrate(ObjectWithRelationshipsWithLimit::class, ['value' => [
1442
            ['value' => 'foo'],
1443
            ['value' => 'bar'],
1444
        ]]);
1445
    }
1446
1447
    /**
1448
     * @group relationships
1449
     */
1450
    public function testHydrateRelationshipsPropertyWithUnstantiableObject(): void
1451
    {
1452
        $this->expectException(UnsupportedPropertyTypeException::class);
1453
        $this->expectExceptionMessage(sprintf(
1454
            'The property %s.%s refers to a non-instantiable class %s.',
1455
            ObjectWithRelationshipsWithUnstantiableObject::class,
1456
            'value',
1457
            UnstantiableObject::class,
1458
        ));
1459
1460
        $this->createHydrator()->hydrate(
1461
            ObjectWithRelationshipsWithUnstantiableObject::class,
1462
            ['value' => [['value' => []]]],
1463
        );
1464
    }
1465
1466
    /**
1467
     * @group json
1468
     */
1469
    public function testHydrateObjectWithJson(): void
1470
    {
1471
        $this->assertInvalidValueExceptionCount(0);
1472
        $object = $this->createHydrator()->hydrateWithJson(ObjectWithString::class, '{"value": "foo"}');
1473
        $this->assertSame('foo', $object->value);
1474
    }
1475
1476
    /**
1477
     * @group json
1478
     */
1479
    public function testHydrateObjectWithInvalidJson(): void
1480
    {
1481
        $this->expectException(InvalidDataException::class);
1482
        $this->expectExceptionMessageMatches('/^The JSON is invalid and couldn‘t be decoded due to: .+$/');
1483
        $this->createHydrator()->hydrateWithJson(ObjectWithString::class, '[[]]', 0, 1);
1484
    }
1485
1486
    /**
1487
     * @group json
1488
     */
1489
    public function testHydrateObjectWithNonObjectableJson(): void
1490
    {
1491
        $this->expectException(InvalidDataException::class);
1492
        $this->expectExceptionMessage('The JSON must be in the form of an array or an object.');
1493
        $this->createHydrator()->hydrateWithJson(ObjectWithString::class, 'null');
1494
    }
1495
1496
    public function testInstantedObject(): void
1497
    {
1498
        $object = new class {
1499
            public string $value;
1500
        };
1501
1502
        $this->assertInvalidValueExceptionCount(0);
1503
        $this->createHydrator()->hydrate($object, ['value' => 'foo']);
1504
        $this->assertSame('foo', $object->value);
1505
    }
1506
1507
    public function testUnstantiableObject(): void
1508
    {
1509
        $this->expectException(UninitializableObjectException::class);
1510
        $this->expectExceptionMessage(sprintf(
1511
            'The class %s cannot be hydrated because it is an uninstantiable class.',
1512
            UnstantiableObject::class,
1513
        ));
1514
1515
        $this->createHydrator()->hydrate(UnstantiableObject::class, []);
1516
    }
1517
1518
    public function testStaticalProperty(): void
1519
    {
1520
        $this->assertInvalidValueExceptionCount(0);
1521
        $this->createHydrator()->hydrate(ObjectWithStaticalProperty::class, ['value' => 'foo']);
1522
        $this->assertNotSame('foo', ObjectWithStaticalProperty::$value);
1523
    }
1524
1525
    public function testIgnoredProperty(): void
1526
    {
1527
        $this->assertInvalidValueExceptionCount(0);
1528
        $object = $this->createHydrator()->hydrate(ObjectWithIgnoredProperty::class, ['value' => 'foo']);
1529
        $this->assertNotSame('foo', $object->value);
1530
    }
1531
1532
    public function testUnsupportedPropertyType(): void
1533
    {
1534
        $this->expectException(UnsupportedPropertyTypeException::class);
1535
        $this->expectExceptionMessage(sprintf(
1536
            'The property %s.%s contains an unsupported type %s.',
1537
            ObjectWithUnsupportedProperty::class,
1538
            'value',
1539
            'iterable',
1540
        ));
1541
1542
        $this->createHydrator()->hydrate(ObjectWithUnsupportedProperty::class, ['value' => []]);
1543
    }
1544
1545
    public function testUnsupportedPropertyTypeNotation(): void
1546
    {
1547
        $this->phpRequired('8.0');
1548
1549
        $this->expectException(UnsupportedPropertyTypeException::class);
1550
        $this->expectExceptionMessage(sprintf(
1551
            'The property %s.%s contains an unsupported type %s.',
1552
            ObjectWithUnsupportedPropertyNotation::class,
1553
            'value',
1554
            'int|float',
1555
        ));
1556
1557
        $this->createHydrator()->hydrate(ObjectWithUnsupportedPropertyNotation::class, ['value' => []]);
1558
    }
1559
1560
    public function testUntypedProperty(): void
1561
    {
1562
        $this->expectException(UntypedPropertyException::class);
1563
        $this->expectExceptionMessage(sprintf(
1564
            'The property %s.%s is not typed.',
1565
            ObjectWithUntypedProperty::class,
1566
            'value',
1567
        ));
1568
1569
        $this->createHydrator()->hydrate(ObjectWithUntypedProperty::class, ['value' => []]);
1570
    }
1571
1572
    public function testAnnotatedAlias(): void
1573
    {
1574
        /** @var Hydrator $hydrator */
1575
        $hydrator = $this->createHydrator();
1576
        $hydrator->useDefaultAnnotationReader();
0 ignored issues
show
Deprecated Code introduced by
The function Sunrise\Hydrator\Hydrato...faultAnnotationReader() has been deprecated: 3.1.0 ( Ignorable by Annotation )

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

1576
        /** @scrutinizer ignore-deprecated */ $hydrator->useDefaultAnnotationReader();

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
1577
1578
        $this->assertInvalidValueExceptionCount(0);
1579
        $object = $hydrator->hydrate(ObjectWithAnnotatedAlias::class, ['non-normalized-value' => 'foo']);
1580
        $this->assertSame('foo', $object->value);
1581
1582
        $this->assertInvalidValueExceptionCount(1);
1583
        $hydrator->hydrate(ObjectWithAnnotatedAlias::class, ['value' => 'foo']);
1584
        $this->assertInvalidValueExceptionMessage(0, 'This value should be provided.');
1585
        $this->assertInvalidValueExceptionErrorCode(0, ErrorCode::VALUE_SHOULD_BE_PROVIDED);
1586
        $this->assertInvalidValueExceptionPropertyPath(0, 'value');
1587
    }
1588
1589
    public function testAttributedAlias(): void
1590
    {
1591
        $this->phpRequired('8.0');
1592
1593
        /** @var Hydrator $hydrator */
1594
        $hydrator = $this->createHydrator();
1595
1596
        $this->assertInvalidValueExceptionCount(0);
1597
        $object = $hydrator->hydrate(ObjectWithAttributedAlias::class, ['non-normalized-value' => 'foo']);
1598
        $this->assertSame('foo', $object->value);
1599
1600
        $this->assertInvalidValueExceptionCount(1);
1601
        $hydrator->hydrate(ObjectWithAttributedAlias::class, ['value' => 'foo']);
1602
        $this->assertInvalidValueExceptionMessage(0, 'This value should be provided.');
1603
        $this->assertInvalidValueExceptionErrorCode(0, ErrorCode::VALUE_SHOULD_BE_PROVIDED);
1604
        $this->assertInvalidValueExceptionPropertyPath(0, 'value');
1605
    }
1606
1607
    public function testHydrateStore(): void
1608
    {
1609
        $this->phpRequired('8.1');
1610
1611
        $sold = Status::SOLD;
1612
1613
        $data = [];
1614
        $data['name'] = 'Some product';
1615
        $data['category']['name'] = 'Some category';
1616
        $data['tags'][]['name'] = 'foo';
1617
        $data['tags'][]['name'] = 'bar';
1618
        $data['status'] = $sold->value;
1619
        $data['createdAt'] = '2000-01-01 00:00:00';
1620
1621
        $this->assertInvalidValueExceptionCount(0);
1622
        $product = $this->createHydrator()->hydrate(Product::class, $data);
1623
        $this->assertSame('Some product', $product->name);
1624
        $this->assertSame('Some category', $product->category->name);
1625
        $this->assertCount(2, $product->tags);
1626
        $this->assertArrayHasKey(0, $product->tags);
1627
        $this->assertSame('foo', $product->tags[0]->name);
1628
        $this->assertArrayHasKey(1, $product->tags);
1629
        $this->assertSame('bar', $product->tags[1]->name);
1630
        $this->assertSame(Status::SOLD, $product->status);
1631
        $this->assertSame('2000-01-01 00:00:00', $product->createdAt->format('Y-m-d H:i:s'));
1632
1633
        unset($data['createdAt']);
1634
        $this->assertInvalidValueExceptionCount(0);
1635
        $product = $this->createHydrator()->hydrate(Product::class, $data);
1636
        $this->assertSame('2020-01-01 12:00:00', $product->createdAt->format('Y-m-d H:i:s'));
1637
    }
1638
1639
    public function testSymfonyViolations(): void
1640
    {
1641
        $violations = null;
1642
        try {
1643
            $this->createHydrator()->hydrate(ObjectWithString::class, []);
1644
        } catch (InvalidDataException $e) {
1645
            $violations = $e->getViolations();
1646
        }
1647
1648
        $this->assertNotNull($violations);
1649
        $this->assertCount(1, $violations);
1650
        $this->assertTrue($violations->has(0));
1651
        $this->assertSame(ErrorCode::VALUE_SHOULD_BE_PROVIDED, $violations->get(0)->getCode());
1652
        $this->assertSame('This value should be provided.', $violations->get(0)->getMessage());
1653
        $this->assertSame('value', $violations->get(0)->getPropertyPath());
1654
    }
1655
1656
    public function emptyDataProvider(): array
1657
    {
1658
        return [
1659
            [[]],
1660
        ];
1661
    }
1662
1663
    public function strictNullDataProvider(): array
1664
    {
1665
        return [
1666
            [['value' => null], null],
1667
        ];
1668
    }
1669
1670
    public function nonStrictNullDataProvider(): array
1671
    {
1672
        return [
1673
            [['value' => ''], null],
1674
            [['value' => ' '], null],
1675
        ];
1676
    }
1677
1678
    public function strictBooleanDataProvider(): array
1679
    {
1680
        return [
1681
            [['value' => true], true],
1682
            [['value' => false], false],
1683
        ];
1684
    }
1685
1686
    public function nonStrictBooleanDataProvider(): array
1687
    {
1688
        return [
1689
            [['value' => '1'], true],
1690
            [['value' => '0'], false],
1691
            [['value' => 'true'], true],
1692
            [['value' => 'false'], false],
1693
            [['value' => 'yes'], true],
1694
            [['value' => 'no'], false],
1695
            [['value' => 'on'], true],
1696
            [['value' => 'off'], false],
1697
        ];
1698
    }
1699
1700
    public function notBooleanDataProvider(): array
1701
    {
1702
        return [
1703
            [['value' => 0]],
1704
            [['value' => 1]],
1705
            [['value' => 42]],
1706
            [['value' => 3.14159]],
1707
            [['value' => 'foo']],
1708
            [['value' => []]],
1709
        ];
1710
    }
1711
1712
    public function strictIntegerDataProvider(): array
1713
    {
1714
        return [
1715
            [['value' => -1], -1],
1716
            [['value' => 0], 0],
1717
            [['value' => 1], 1],
1718
        ];
1719
    }
1720
1721
    public function nonStrictIntegerDataProvider(): array
1722
    {
1723
        return [
1724
            [['value' => '-1'], -1],
1725
            [['value' => '0'], 0],
1726
            [['value' => '+1'], 1],
1727
        ];
1728
    }
1729
1730
    public function notIntegerDataProvider(): array
1731
    {
1732
        return [
1733
            [['value' => true]],
1734
            [['value' => false]],
1735
            [['value' => 3.14159]],
1736
            [['value' => 'foo']],
1737
            [['value' => []]],
1738
        ];
1739
    }
1740
1741
    public function strictNumberDataProvider(): array
1742
    {
1743
        return [
1744
            [['value' => -1], -1.],
1745
            [['value' => 0], 0.],
1746
            [['value' => 1], 1.],
1747
            [['value' => -1.], -1.],
1748
            [['value' => 0.], 0.],
1749
            [['value' => 1.], 1.],
1750
            [['value' => -.1], -.1],
1751
            [['value' => .0], .0],
1752
            [['value' => .1], .1],
1753
        ];
1754
    }
1755
1756
    public function nonStrictNumberDataProvider(): array
1757
    {
1758
        return [
1759
            [['value' => '-1'], -1.],
1760
            [['value' => '0'], 0.],
1761
            [['value' => '+1'], 1.],
1762
            [['value' => '-1.'], -1.],
1763
            [['value' => '0.'], 0.],
1764
            [['value' => '+1.'], 1.],
1765
            [['value' => '-.1'], -.1],
1766
            [['value' => '.0'], .0],
1767
            [['value' => '+.1'], .1],
1768
            [['value' => '-1.0'], -1.],
1769
            [['value' => '0.0'], 0.],
1770
            [['value' => '+1.0'], 1.],
1771
            [['value' => '1e-1'], .1],
1772
            [['value' => '1e1'], 10.],
1773
            [['value' => '1e+1'], 10.],
1774
            [['value' => '1.e-1'], .1],
1775
            [['value' => '1.e1'], 10.],
1776
            [['value' => '1.e+1'], 10.],
1777
            [['value' => '.1e-1'], .01],
1778
            [['value' => '.1e1'], 1.],
1779
            [['value' => '.1e+1'], 1.],
1780
            [['value' => '1.0e-1'], .1],
1781
            [['value' => '1.0e1'], 10.],
1782
            [['value' => '1.0e+1'], 10.],
1783
        ];
1784
    }
1785
1786
    public function notNumberDataProvider(): array
1787
    {
1788
        return [
1789
            [['value' => true]],
1790
            [['value' => false]],
1791
            [['value' => 'foo']],
1792
            [['value' => []]],
1793
        ];
1794
    }
1795
1796
    public function stringDataProvider(): array
1797
    {
1798
        return [
1799
            [['value' => 'foo'], 'foo'],
1800
1801
            // Should not be cast to a null
1802
            [['value' => ''], ''],
1803
            [['value' => ' '], ' '],
1804
1805
            // Should not be cast to a boolean type
1806
            [['value' => '1'], '1'],
1807
            [['value' => '0'], '0'],
1808
            [['value' => 'true'], 'true'],
1809
            [['value' => 'false'], 'false'],
1810
            [['value' => 'yes'], 'yes'],
1811
            [['value' => 'no'], 'no'],
1812
            [['value' => 'on'], 'on'],
1813
            [['value' => 'off'], 'off'],
1814
1815
            // Should not be cast to a number
1816
            [['value' => '42'], '42'],
1817
            [['value' => '3.14159'], '3.14159'],
1818
        ];
1819
    }
1820
1821
    public function notStringDataProvider(): array
1822
    {
1823
        return [
1824
            [['value' => true]],
1825
            [['value' => false]],
1826
            [['value' => 42]],
1827
            [['value' => 3.14159]],
1828
            [['value' => []]],
1829
        ];
1830
    }
1831
1832
    public function arrayDataProvider(): array
1833
    {
1834
        return [
1835
            [['value' => ['foo']], ['foo']]
1836
        ];
1837
    }
1838
1839
    public function notArrayDataProvider(): array
1840
    {
1841
        return [
1842
            [['value' => true]],
1843
            [['value' => false]],
1844
            [['value' => 42]],
1845
            [['value' => 3.14159]],
1846
            [['value' => 'foo']],
1847
        ];
1848
    }
1849
1850
    public function timestampDataProvider(): array
1851
    {
1852
        return [
1853
            [['value' => '1970-01-01 00:00:00'], '1970-01-01 00:00:00', 'Y-m-d H:i:s'],
1854
        ];
1855
    }
1856
1857
    public function invalidTimestampDataProvider(): array
1858
    {
1859
        return [
1860
            [['value' => 'Tue, 06 Jun 23 16:50:23']],
1861
        ];
1862
    }
1863
1864
    public function notTimestampDataProvider(): array
1865
    {
1866
        return [
1867
            [['value' => true]],
1868
            [['value' => false]],
1869
            [['value' => 42]],
1870
            [['value' => 3.14159]],
1871
            [['value' => []]],
1872
        ];
1873
    }
1874
1875
    public function strictUnixTimeStampDataProvider(): array
1876
    {
1877
        return [
1878
            [['value' => -1], '1969-12-31 23:59:59', 'Y-m-d H:i:s'],
1879
            [['value' => 0], '1970-01-01 00:00:00', 'Y-m-d H:i:s'],
1880
            [['value' => 1], '1970-01-01 00:00:01', 'Y-m-d H:i:s'],
1881
        ];
1882
    }
1883
1884
    public function nonStrictUnixTimeStampDataProvider(): array
1885
    {
1886
        return [
1887
            [['value' => '-1'], '1969-12-31 23:59:59', 'Y-m-d H:i:s'],
1888
            [['value' => '0'], '1970-01-01 00:00:00', 'Y-m-d H:i:s'],
1889
            [['value' => '1'], '1970-01-01 00:00:01', 'Y-m-d H:i:s'],
1890
        ];
1891
    }
1892
1893
    public function notUnixTimeStampDataProvider(): array
1894
    {
1895
        return [
1896
            [['value' => true]],
1897
            [['value' => false]],
1898
            [['value' => 'foo']],
1899
            [['value' => []]],
1900
        ];
1901
    }
1902
1903
    public function timezoneDataProvider(): array
1904
    {
1905
        return [
1906
            [['value' => 'Europe/Belgrade'], 'Europe/Belgrade'],
1907
        ];
1908
    }
1909
1910
    public function invalidTimezoneDataProvider(): array
1911
    {
1912
        return [
1913
            [['value' => 'Haafingar/Solitude']],
1914
        ];
1915
    }
1916
1917
    public function uidDataProvider(): array
1918
    {
1919
        return [
1920
            [['value' => '207ddb61-c300-4368-9f26-33d0a99eac00'], '207ddb61-c300-4368-9f26-33d0a99eac00'],
1921
        ];
1922
    }
1923
1924
    public function invalidUidDataProvider(): array
1925
    {
1926
        return [
1927
            [['value' => '207ddb61-c300-4368-9f26-33d0a99eac0']],
1928
            [['value' => '207ddb61-c300-4368-9f26-33d0a99eac0x']],
1929
            [['value' => '207ddb61-c300-4368-9f26-33d0a99eac000']],
1930
        ];
1931
    }
1932
1933
    public function strictIntegerEnumerationDataProvider(): array
1934
    {
1935
        if (PHP_VERSION_ID < 80100) {
1936
            return [[[], null]];
1937
        }
1938
1939
        $foo = IntegerEnum::FOO;
1940
        $bar = IntegerEnum::BAR;
1941
        $baz = IntegerEnum::BAZ;
1942
1943
        return [
1944
            [['value' => $foo->value], $foo],
1945
            [['value' => $bar->value], $bar],
1946
            [['value' => $baz->value], $baz],
1947
        ];
1948
    }
1949
1950
    public function nonStrictIntegerEnumerationDataProvider(): array
1951
    {
1952
        if (PHP_VERSION_ID < 80100) {
1953
            return [[[], null]];
1954
        }
1955
1956
        $foo = IntegerEnum::FOO;
1957
        $bar = IntegerEnum::BAR;
1958
        $baz = IntegerEnum::BAZ;
1959
1960
        return [
1961
            [['value' => (string) $foo->value], $foo],
1962
            [['value' => (string) $bar->value], $bar],
1963
            [['value' => (string) $baz->value], $baz],
1964
        ];
1965
    }
1966
1967
    public function stringEnumerationDataProvider(): array
1968
    {
1969
        if (PHP_VERSION_ID < 80100) {
1970
            return [[[], null]];
1971
        }
1972
1973
        $foo = StringEnum::FOO;
1974
        $bar = StringEnum::BAR;
1975
        $baz = StringEnum::BAZ;
1976
1977
        return [
1978
            [['value' => $foo->value], $foo],
1979
            [['value' => $bar->value], $bar],
1980
            [['value' => $baz->value], $baz],
1981
        ];
1982
    }
1983
1984
    public function testIssue25(): void
1985
    {
1986
        $this->assertInvalidValueExceptionCount(1);
1987
        $this->assertInvalidValueExceptionMessage(0, 'This value should be provided.');
1988
        $this->assertInvalidValueExceptionErrorCode(0, ErrorCode::VALUE_SHOULD_BE_PROVIDED);
1989
        $this->assertInvalidValueExceptionPropertyPath(0, 'foo');
1990
        $this->createHydrator()->hydrate(Issue25::class, []);
1991
    }
1992
1993
    private function createHydrator(): HydratorInterface
1994
    {
1995
        $hydrator = new Hydrator();
1996
        if (PHP_VERSION_ID < 80000) {
1997
            $hydrator->useDefaultAnnotationReader();
0 ignored issues
show
Deprecated Code introduced by
The function Sunrise\Hydrator\Hydrato...faultAnnotationReader() has been deprecated: 3.1.0 ( Ignorable by Annotation )

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

1997
            /** @scrutinizer ignore-deprecated */ $hydrator->useDefaultAnnotationReader();

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
1998
        }
1999
2000
        return $hydrator;
2001
    }
2002
2003
    private function phpRequired(string $version): void
2004
    {
2005
        if (version_compare(PHP_VERSION, $version, '<')) {
2006
            $this->markTestSkipped(sprintf('PHP %s is required.', $version));
2007
        }
2008
    }
2009
2010
    private function assertInvalidValueExceptionCount(int $expectedCount): void
2011
    {
2012
        $this->invalidValueExceptionCount = $expectedCount;
2013
    }
2014
2015
    private function assertInvalidValueExceptionMessage(int $exceptionIndex, string $expectedMessage): void
2016
    {
2017
        $this->invalidValueExceptionMessage[] = [$exceptionIndex, $expectedMessage];
2018
    }
2019
2020
    private function assertInvalidValueExceptionPropertyPath(int $exceptionIndex, string $expectedPropertyPath): void
2021
    {
2022
        $this->invalidValueExceptionPropertyPath[] = [$exceptionIndex, $expectedPropertyPath];
2023
    }
2024
2025
    private function assertInvalidValueExceptionErrorCode(int $exceptionIndex, string $expectedErrorCode): void
2026
    {
2027
        $this->invalidValueExceptionErrorCode[] = [$exceptionIndex, $expectedErrorCode];
2028
    }
2029
2030
    protected function runTest(): void
2031
    {
2032
        $invalidDataExceptionHandled = false;
2033
2034
        try {
2035
            parent::runTest();
2036
        } catch (InvalidDataException $invalidDataException) {
2037
            $invalidDataExceptionMessages = [];
2038
            foreach ($invalidDataException->getExceptions() as $invalidValueException) {
2039
                $invalidDataExceptionMessages[] = sprintf(
2040
                    '[%s] %s',
2041
                    $invalidValueException->getPropertyPath(),
2042
                    $invalidValueException->getMessage(),
2043
                );
2044
            }
2045
2046
            if (isset($this->invalidValueExceptionCount)) {
2047
                $invalidDataExceptionHandled = true;
2048
                $this->assertCount(
2049
                    $this->invalidValueExceptionCount,
0 ignored issues
show
Bug introduced by
It seems like $this->invalidValueExceptionCount can also be of type null; however, parameter $expectedCount of PHPUnit\Framework\Assert::assertCount() does only seem to accept integer, maybe add an additional type check? ( Ignorable by Annotation )

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

2049
                    /** @scrutinizer ignore-type */ $this->invalidValueExceptionCount,
Loading history...
2050
                    $invalidDataException->getExceptions(),
2051
                    \join(\PHP_EOL, $invalidDataExceptionMessages),
2052
                );
2053
            }
2054
2055
            foreach ($this->invalidValueExceptionMessage as [$index, $invalidValueExceptionMessage]) {
2056
                $invalidDataExceptionHandled = true;
2057
                $this->assertArrayHasKey($index, $invalidDataException->getExceptions());
2058
                $this->assertSame(
2059
                    $invalidValueExceptionMessage,
2060
                    $invalidDataException->getExceptions()[$index]->getMessage(),
2061
                );
2062
            }
2063
2064
            foreach ($this->invalidValueExceptionPropertyPath as [$index, $invalidValueExceptionPropertyPath]) {
2065
                $invalidDataExceptionHandled = true;
2066
                $this->assertArrayHasKey($index, $invalidDataException->getExceptions());
2067
                $this->assertSame(
2068
                    $invalidValueExceptionPropertyPath,
2069
                    $invalidDataException->getExceptions()[$index]->getPropertyPath(),
2070
                );
2071
            }
2072
2073
            foreach ($this->invalidValueExceptionErrorCode as [$index, $invalidValueExceptionErrorCode]) {
2074
                $invalidDataExceptionHandled = true;
2075
                $this->assertArrayHasKey($index, $invalidDataException->getExceptions());
2076
                $this->assertSame(
2077
                    $invalidValueExceptionErrorCode,
2078
                    $invalidDataException->getExceptions()[$index]->getErrorCode(),
2079
                );
2080
            }
2081
2082
            if (!$invalidDataExceptionHandled) {
2083
                throw $invalidDataException;
2084
            }
2085
        } finally {
2086
            $this->invalidValueExceptionCount = null;
2087
            $this->invalidValueExceptionMessage = [];
2088
            $this->invalidValueExceptionPropertyPath = [];
2089
            $this->invalidValueExceptionErrorCode = [];
2090
2091
            if ($invalidDataExceptionHandled) {
2092
                $this->assertTrue(true);
2093
            }
2094
        }
2095
    }
2096
}
2097