Passed
Pull Request — main (#26)
by Anatoly
39:48
created

HydratorTest.php$0 ➔ testIssue25()   A

Complexity

Conditions 1

Size

Total Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

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

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
1342
1343
        $reader = $this->createMock(Reader::class);
1344
        $hydrator->setAnnotationReader($reader);
1345
        $this->assertSame($reader, $hydrator->getAnnotationReader());
1346
1347
        $hydrator->setAnnotationReader(null);
1348
        $this->assertNull($hydrator->getAnnotationReader());
0 ignored issues
show
Bug introduced by
Are you sure the usage of $hydrator->getAnnotationReader() targeting Sunrise\Hydrator\Hydrator::getAnnotationReader() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
1349
    }
1350
1351
    public function testHydrateStore(): void
1352
    {
1353
        $this->phpRequired('8.1');
1354
1355
        $sold = Status::SOLD;
1356
1357
        $data = [];
1358
        $data['name'] = 'Some product';
1359
        $data['category']['name'] = 'Some category';
1360
        $data['tags'][]['name'] = 'foo';
1361
        $data['tags'][]['name'] = 'bar';
1362
        $data['status'] = $sold->value;
1363
        $data['createdAt'] = '2000-01-01 00:00:00';
1364
1365
        $this->assertInvalidValueExceptionCount(0);
1366
        $product = $this->createHydrator()->hydrate(Product::class, $data);
1367
        $this->assertSame('Some product', $product->name);
1368
        $this->assertSame('Some category', $product->category->name);
1369
        $this->assertCount(2, $product->tags);
1370
        $this->assertArrayHasKey(0, $product->tags);
1371
        $this->assertSame('foo', $product->tags[0]->name);
1372
        $this->assertArrayHasKey(1, $product->tags);
1373
        $this->assertSame('bar', $product->tags[1]->name);
1374
        $this->assertSame(Status::SOLD, $product->status);
1375
        $this->assertSame('2000-01-01 00:00:00', $product->createdAt->format('Y-m-d H:i:s'));
1376
1377
        unset($data['createdAt']);
1378
        $this->assertInvalidValueExceptionCount(0);
1379
        $product = $this->createHydrator()->hydrate(Product::class, $data);
1380
        $this->assertSame('2020-01-01 12:00:00', $product->createdAt->format('Y-m-d H:i:s'));
1381
    }
1382
1383
    public function testSymfonyViolations(): void
1384
    {
1385
        $violations = null;
1386
        try {
1387
            $this->createHydrator()->hydrate(ObjectWithString::class, []);
1388
        } catch (InvalidDataException $e) {
1389
            $violations = $e->getViolations();
1390
        }
1391
1392
        $this->assertNotNull($violations);
1393
        $this->assertCount(1, $violations);
1394
        $this->assertTrue($violations->has(0));
1395
        $this->assertSame(ErrorCode::VALUE_SHOULD_BE_PROVIDED, $violations->get(0)->getCode());
1396
        $this->assertSame('This value should be provided.', $violations->get(0)->getMessage());
1397
        $this->assertSame('value', $violations->get(0)->getPropertyPath());
1398
    }
1399
1400
    public function emptyDataProvider(): array
1401
    {
1402
        return [
1403
            [[]],
1404
        ];
1405
    }
1406
1407
    public function strictNullDataProvider(): array
1408
    {
1409
        return [
1410
            [['value' => null], null],
1411
        ];
1412
    }
1413
1414
    public function nonStrictNullDataProvider(): array
1415
    {
1416
        return [
1417
            [['value' => ''], null],
1418
            [['value' => ' '], null],
1419
        ];
1420
    }
1421
1422
    public function strictBooleanDataProvider(): array
1423
    {
1424
        return [
1425
            [['value' => true], true],
1426
            [['value' => false], false],
1427
        ];
1428
    }
1429
1430
    public function nonStrictBooleanDataProvider(): array
1431
    {
1432
        return [
1433
            [['value' => '1'], true],
1434
            [['value' => '0'], false],
1435
            [['value' => 'true'], true],
1436
            [['value' => 'false'], false],
1437
            [['value' => 'yes'], true],
1438
            [['value' => 'no'], false],
1439
            [['value' => 'on'], true],
1440
            [['value' => 'off'], false],
1441
        ];
1442
    }
1443
1444
    public function notBooleanDataProvider(): array
1445
    {
1446
        return [
1447
            [['value' => 0]],
1448
            [['value' => 1]],
1449
            [['value' => 42]],
1450
            [['value' => 3.14159]],
1451
            [['value' => 'foo']],
1452
            [['value' => []]],
1453
        ];
1454
    }
1455
1456
    public function strictIntegerDataProvider(): array
1457
    {
1458
        return [
1459
            [['value' => -1], -1],
1460
            [['value' => 0], 0],
1461
            [['value' => 1], 1],
1462
        ];
1463
    }
1464
1465
    public function nonStrictIntegerDataProvider(): array
1466
    {
1467
        return [
1468
            [['value' => '-1'], -1],
1469
            [['value' => '0'], 0],
1470
            [['value' => '+1'], 1],
1471
        ];
1472
    }
1473
1474
    public function notIntegerDataProvider(): array
1475
    {
1476
        return [
1477
            [['value' => true]],
1478
            [['value' => false]],
1479
            [['value' => 3.14159]],
1480
            [['value' => 'foo']],
1481
            [['value' => []]],
1482
        ];
1483
    }
1484
1485
    public function strictNumberDataProvider(): array
1486
    {
1487
        return [
1488
            [['value' => -1], -1.],
1489
            [['value' => 0], 0.],
1490
            [['value' => 1], 1.],
1491
            [['value' => -1.], -1.],
1492
            [['value' => 0.], 0.],
1493
            [['value' => 1.], 1.],
1494
            [['value' => -.1], -.1],
1495
            [['value' => .0], .0],
1496
            [['value' => .1], .1],
1497
        ];
1498
    }
1499
1500
    public function nonStrictNumberDataProvider(): array
1501
    {
1502
        return [
1503
            [['value' => '-1'], -1.],
1504
            [['value' => '0'], 0.],
1505
            [['value' => '+1'], 1.],
1506
            [['value' => '-1.'], -1.],
1507
            [['value' => '0.'], 0.],
1508
            [['value' => '+1.'], 1.],
1509
            [['value' => '-.1'], -.1],
1510
            [['value' => '.0'], .0],
1511
            [['value' => '+.1'], .1],
1512
            [['value' => '-1.0'], -1.],
1513
            [['value' => '0.0'], 0.],
1514
            [['value' => '+1.0'], 1.],
1515
            [['value' => '1e-1'], .1],
1516
            [['value' => '1e1'], 10.],
1517
            [['value' => '1e+1'], 10.],
1518
            [['value' => '1.e-1'], .1],
1519
            [['value' => '1.e1'], 10.],
1520
            [['value' => '1.e+1'], 10.],
1521
            [['value' => '.1e-1'], .01],
1522
            [['value' => '.1e1'], 1.],
1523
            [['value' => '.1e+1'], 1.],
1524
            [['value' => '1.0e-1'], .1],
1525
            [['value' => '1.0e1'], 10.],
1526
            [['value' => '1.0e+1'], 10.],
1527
        ];
1528
    }
1529
1530
    public function notNumberDataProvider(): array
1531
    {
1532
        return [
1533
            [['value' => true]],
1534
            [['value' => false]],
1535
            [['value' => 'foo']],
1536
            [['value' => []]],
1537
        ];
1538
    }
1539
1540
    public function stringDataProvider(): array
1541
    {
1542
        return [
1543
            [['value' => 'foo'], 'foo'],
1544
1545
            // Should not be cast to a null
1546
            [['value' => ''], ''],
1547
            [['value' => ' '], ' '],
1548
1549
            // Should not be cast to a boolean type
1550
            [['value' => '1'], '1'],
1551
            [['value' => '0'], '0'],
1552
            [['value' => 'true'], 'true'],
1553
            [['value' => 'false'], 'false'],
1554
            [['value' => 'yes'], 'yes'],
1555
            [['value' => 'no'], 'no'],
1556
            [['value' => 'on'], 'on'],
1557
            [['value' => 'off'], 'off'],
1558
1559
            // Should not be cast to a number
1560
            [['value' => '42'], '42'],
1561
            [['value' => '3.14159'], '3.14159'],
1562
        ];
1563
    }
1564
1565
    public function notStringDataProvider(): array
1566
    {
1567
        return [
1568
            [['value' => true]],
1569
            [['value' => false]],
1570
            [['value' => 42]],
1571
            [['value' => 3.14159]],
1572
            [['value' => []]],
1573
        ];
1574
    }
1575
1576
    public function arrayDataProvider(): array
1577
    {
1578
        return [
1579
            [['value' => ['foo']], ['foo']]
1580
        ];
1581
    }
1582
1583
    public function notArrayDataProvider(): array
1584
    {
1585
        return [
1586
            [['value' => true]],
1587
            [['value' => false]],
1588
            [['value' => 42]],
1589
            [['value' => 3.14159]],
1590
            [['value' => 'foo']],
1591
        ];
1592
    }
1593
1594
    public function timestampDataProvider(): array
1595
    {
1596
        return [
1597
            [['value' => '1970-01-01 00:00:00'], '1970-01-01 00:00:00', 'Y-m-d H:i:s'],
1598
        ];
1599
    }
1600
1601
    public function invalidTimestampDataProvider(): array
1602
    {
1603
        return [
1604
            [['value' => 'Tue, 06 Jun 23 16:50:23']],
1605
        ];
1606
    }
1607
1608
    public function notTimestampDataProvider(): array
1609
    {
1610
        return [
1611
            [['value' => true]],
1612
            [['value' => false]],
1613
            [['value' => 42]],
1614
            [['value' => 3.14159]],
1615
            [['value' => []]],
1616
        ];
1617
    }
1618
1619
    public function strictUnixTimeStampDataProvider(): array
1620
    {
1621
        return [
1622
            [['value' => -1], '1969-12-31 23:59:59', 'Y-m-d H:i:s'],
1623
            [['value' => 0], '1970-01-01 00:00:00', 'Y-m-d H:i:s'],
1624
            [['value' => 1], '1970-01-01 00:00:01', 'Y-m-d H:i:s'],
1625
        ];
1626
    }
1627
1628
    public function nonStrictUnixTimeStampDataProvider(): array
1629
    {
1630
        return [
1631
            [['value' => '-1'], '1969-12-31 23:59:59', 'Y-m-d H:i:s'],
1632
            [['value' => '0'], '1970-01-01 00:00:00', 'Y-m-d H:i:s'],
1633
            [['value' => '1'], '1970-01-01 00:00:01', 'Y-m-d H:i:s'],
1634
        ];
1635
    }
1636
1637
    public function notUnixTimeStampDataProvider(): array
1638
    {
1639
        return [
1640
            [['value' => true]],
1641
            [['value' => false]],
1642
            [['value' => 'foo']],
1643
            [['value' => []]],
1644
        ];
1645
    }
1646
1647
    public function strictIntegerEnumerationDataProvider(): array
1648
    {
1649
        if (PHP_VERSION_ID < 80100) {
1650
            return [[[], null]];
1651
        }
1652
1653
        $foo = IntegerEnum::FOO;
1654
        $bar = IntegerEnum::BAR;
1655
        $baz = IntegerEnum::BAZ;
1656
1657
        return [
1658
            [['value' => $foo->value], $foo],
1659
            [['value' => $bar->value], $bar],
1660
            [['value' => $baz->value], $baz],
1661
        ];
1662
    }
1663
1664
    public function nonStrictIntegerEnumerationDataProvider(): array
1665
    {
1666
        if (PHP_VERSION_ID < 80100) {
1667
            return [[[], null]];
1668
        }
1669
1670
        $foo = IntegerEnum::FOO;
1671
        $bar = IntegerEnum::BAR;
1672
        $baz = IntegerEnum::BAZ;
1673
1674
        return [
1675
            [['value' => (string) $foo->value], $foo],
1676
            [['value' => (string) $bar->value], $bar],
1677
            [['value' => (string) $baz->value], $baz],
1678
        ];
1679
    }
1680
1681
    public function stringEnumerationDataProvider(): array
1682
    {
1683
        if (PHP_VERSION_ID < 80100) {
1684
            return [[[], null]];
1685
        }
1686
1687
        $foo = StringEnum::FOO;
1688
        $bar = StringEnum::BAR;
1689
        $baz = StringEnum::BAZ;
1690
1691
        return [
1692
            [['value' => $foo->value], $foo],
1693
            [['value' => $bar->value], $bar],
1694
            [['value' => $baz->value], $baz],
1695
        ];
1696
    }
1697
1698
    public function testIssue25(): void
1699
    {
1700
        $this->assertInvalidValueExceptionCount(1);
1701
        $this->assertInvalidValueExceptionMessage(0, 'This value should be provided.');
1702
        $this->assertInvalidValueExceptionErrorCode(0, ErrorCode::VALUE_SHOULD_BE_PROVIDED);
1703
        $this->assertInvalidValueExceptionPropertyPath(0, 'foo');
1704
        $this->createHydrator()->hydrate(Issue25::class, []);
1705
    }
1706
1707
    private function createHydrator(): HydratorInterface
1708
    {
1709
        $hydrator = new Hydrator();
1710
        if (PHP_VERSION_ID < 80000) {
1711
            $hydrator->useDefaultAnnotationReader();
1712
        }
1713
1714
        return $hydrator;
1715
    }
1716
1717
    private function phpRequired(string $version): void
1718
    {
1719
        if (version_compare(PHP_VERSION, $version, '<')) {
1720
            $this->markTestSkipped(sprintf('PHP %s is required.', $version));
1721
        }
1722
    }
1723
1724
    private function assertInvalidValueExceptionCount(int $expectedCount): void
1725
    {
1726
        $this->invalidValueExceptionCount = $expectedCount;
1727
    }
1728
1729
    private function assertInvalidValueExceptionMessage(int $exceptionIndex, string $expectedMessage): void
1730
    {
1731
        $this->invalidValueExceptionMessage[] = [$exceptionIndex, $expectedMessage];
1732
    }
1733
1734
    private function assertInvalidValueExceptionPropertyPath(int $exceptionIndex, string $expectedPropertyPath): void
1735
    {
1736
        $this->invalidValueExceptionPropertyPath[] = [$exceptionIndex, $expectedPropertyPath];
1737
    }
1738
1739
    private function assertInvalidValueExceptionErrorCode(int $exceptionIndex, string $expectedErrorCode): void
1740
    {
1741
        $this->invalidValueExceptionErrorCode[] = [$exceptionIndex, $expectedErrorCode];
1742
    }
1743
1744
    protected function runTest(): void
1745
    {
1746
        $invalidDataExceptionHandled = false;
1747
1748
        try {
1749
            parent::runTest();
1750
        } catch (InvalidDataException $invalidDataException) {
1751
            $invalidDataExceptionMessages = [];
1752
            foreach ($invalidDataException->getExceptions() as $invalidValueException) {
1753
                $invalidDataExceptionMessages[] = sprintf(
1754
                    '[%s] %s',
1755
                    $invalidValueException->getPropertyPath(),
1756
                    $invalidValueException->getMessage(),
1757
                );
1758
            }
1759
1760
            if (isset($this->invalidValueExceptionCount)) {
1761
                $invalidDataExceptionHandled = true;
1762
                $this->assertCount(
1763
                    $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

1763
                    /** @scrutinizer ignore-type */ $this->invalidValueExceptionCount,
Loading history...
1764
                    $invalidDataException->getExceptions(),
1765
                    \join(\PHP_EOL, $invalidDataExceptionMessages),
1766
                );
1767
            }
1768
1769
            foreach ($this->invalidValueExceptionMessage as [$index, $invalidValueExceptionMessage]) {
1770
                $invalidDataExceptionHandled = true;
1771
                $this->assertArrayHasKey($index, $invalidDataException->getExceptions());
1772
                $this->assertSame(
1773
                    $invalidValueExceptionMessage,
1774
                    $invalidDataException->getExceptions()[$index]->getMessage(),
1775
                );
1776
            }
1777
1778
            foreach ($this->invalidValueExceptionPropertyPath as [$index, $invalidValueExceptionPropertyPath]) {
1779
                $invalidDataExceptionHandled = true;
1780
                $this->assertArrayHasKey($index, $invalidDataException->getExceptions());
1781
                $this->assertSame(
1782
                    $invalidValueExceptionPropertyPath,
1783
                    $invalidDataException->getExceptions()[$index]->getPropertyPath(),
1784
                );
1785
            }
1786
1787
            foreach ($this->invalidValueExceptionErrorCode as [$index, $invalidValueExceptionErrorCode]) {
1788
                $invalidDataExceptionHandled = true;
1789
                $this->assertArrayHasKey($index, $invalidDataException->getExceptions());
1790
                $this->assertSame(
1791
                    $invalidValueExceptionErrorCode,
1792
                    $invalidDataException->getExceptions()[$index]->getErrorCode(),
1793
                );
1794
            }
1795
1796
            if (!$invalidDataExceptionHandled) {
1797
                throw $invalidDataException;
1798
            }
1799
        } finally {
1800
            $this->invalidValueExceptionCount = null;
1801
            $this->invalidValueExceptionMessage = [];
1802
            $this->invalidValueExceptionPropertyPath = [];
1803
            $this->invalidValueExceptionErrorCode = [];
1804
1805
            if ($invalidDataExceptionHandled) {
1806
                $this->assertTrue(true);
1807
            }
1808
        }
1809
    }
1810
}
1811