Test Failed
Pull Request — main (#37)
by Anatoly
50:43
created

testHydrateUnstantiableAssociationProperty()

Size

Total Lines 8
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 4
c 0
b 0
f 0
nc 1
nop 0
dl 0
loc 8
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Sunrise\Hydrator\Tests;
6
7
use DateTimeImmutable;
8
use DateTimeZone;
9
use Generator;
10
use PHPUnit\Framework\TestCase;
11
use ReflectionClass;
12
use ReflectionFunction;
13
use ReflectionIntersectionType;
0 ignored issues
show
Bug introduced by
The type ReflectionIntersectionType was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
14
use ReflectionMethod;
15
use ReflectionNamedType;
16
use ReflectionParameter;
17
use ReflectionUnionType;
18
use Sunrise\Hydrator\Annotation\Alias;
19
use Sunrise\Hydrator\Annotation\Ignore;
20
use Sunrise\Hydrator\Annotation\Subtype;
21
use Sunrise\Hydrator\Dictionary\BuiltinType;
22
use Sunrise\Hydrator\Dictionary\ContextKey;
23
use Sunrise\Hydrator\Dictionary\ErrorCode;
24
use Sunrise\Hydrator\Exception\InvalidDataException;
25
use Sunrise\Hydrator\Exception\InvalidObjectException;
26
use Sunrise\Hydrator\Hydrator;
27
use Sunrise\Hydrator\HydratorInterface;
28
use Sunrise\Hydrator\Type;
29
use Sunrise\Hydrator\TypeConverter\TimestampTypeConverter;
30
31
use function date;
32
use function get_class;
33
use function join;
34
use function sprintf;
35
use function version_compare;
36
37
use const PHP_VERSION;
38
use const PHP_VERSION_ID;
39
40
class HydratorTest extends TestCase
41
{
42
    private ?int $invalidValueExceptionCount = null;
43
    private array $invalidValueExceptionMessage = [];
44
    private array $invalidValueExceptionPropertyPath = [];
45
    private array $invalidValueExceptionErrorCode = [];
46
47
    public function testIssue25(): void
48
    {
49
        $this->assertInvalidValueExceptionCount(1);
50
        $this->assertInvalidValueExceptionMessage(0, 'This value must be provided.');
51
        $this->assertInvalidValueExceptionErrorCode(0, ErrorCode::MUST_BE_PROVIDED);
52
        $this->assertInvalidValueExceptionPropertyPath(0, 'foo');
53
        $this->createHydrator()->hydrate(Fixture\Issue25::class, []);
54
    }
55
56
    public function testStdClassWithArrayProperty(): void
57
    {
58
        $object = new class {
59
            public array $value;
60
        };
61
62
        $this->assertInvalidValueExceptionCount(0);
63
        $this->createHydrator()->hydrate($object, ['value' => (object) ['foo' => 'bar']]);
64
        $this->assertSame(['foo' => 'bar'], $object->value);
65
    }
66
67
    public function testStdClassWithArrayAccessProperty(): void
68
    {
69
        $object = new class {
70
            public Fixture\Collection $value;
71
        };
72
73
        $this->assertInvalidValueExceptionCount(0);
74
        $this->createHydrator()->hydrate($object, ['value' => (object) ['foo' => 'bar']]);
75
        $this->assertSame(['foo' => 'bar'], $object->value->elements);
76
    }
77
78
    public function testStdClassWithAssociationProperty(): void
79
    {
80
        $object = new class {
81
            public Fixture\StringAssociation $value;
82
        };
83
84
        $this->assertInvalidValueExceptionCount(0);
85
        $this->createHydrator()->hydrate($object, ['value' => (object) ['value' => 'foo']]);
86
        $this->assertSame('foo', $object->value->value);
87
    }
88
89
    /**
90
     * @group boolean
91
     * @dataProvider strictBooleanDataProvider
92
     * @dataProvider nonStrictBooleanDataProvider
93
     */
94
    public function testHydrateBooleanProperty(array $data, bool $expected): void
95
    {
96
        $object = new class {
97
            public bool $value;
98
        };
99
100
        $this->assertInvalidValueExceptionCount(0);
101
        $this->createHydrator()->hydrate($object, $data);
102
        $this->assertSame($expected, $object->value);
103
    }
104
105
    /**
106
     * @group boolean
107
     * @dataProvider strictBooleanDataProvider
108
     * @dataProvider nonStrictBooleanDataProvider
109
     * @dataProvider strictNullDataProvider
110
     * @dataProvider nonStrictNullDataProvider
111
     */
112
    public function testHydrateNullableBooleanProperty(array $data, ?bool $expected): void
113
    {
114
        $object = new class {
115
            public ?bool $value;
116
        };
117
118
        $this->assertInvalidValueExceptionCount(0);
119
        $this->createHydrator()->hydrate($object, $data);
120
        $this->assertSame($expected, $object->value);
121
    }
122
123
    /**
124
     * @group boolean
125
     * @dataProvider strictBooleanDataProvider
126
     * @dataProvider nonStrictBooleanDataProvider
127
     * @dataProvider emptyArrayProvider
128
     */
129
    public function testHydrateOptionalBooleanProperty(array $data, bool $expected = true): void
130
    {
131
        $object = new class {
132
            public bool $value = true;
133
        };
134
135
        $this->assertInvalidValueExceptionCount(0);
136
        $this->createHydrator()->hydrate($object, $data);
137
        $this->assertSame($expected, $object->value);
138
    }
139
140
    /**
141
     * @group boolean
142
     * @dataProvider strictNullDataProvider
143
     * @dataProvider nonStrictNullDataProvider
144
     */
145
    public function testHydrateBooleanPropertyWithEmptyValue(array $data): void
146
    {
147
        $object = new class {
148
            public bool $value;
149
        };
150
151
        $this->assertInvalidValueExceptionCount(1);
152
        $this->assertInvalidValueExceptionMessage(0, 'This value must not be empty.');
153
        $this->assertInvalidValueExceptionErrorCode(0, ErrorCode::MUST_NOT_BE_EMPTY);
154
        $this->assertInvalidValueExceptionPropertyPath(0, 'value');
155
        $this->createHydrator()->hydrate($object, $data);
156
    }
157
158
    /**
159
     * @group boolean
160
     * @dataProvider notBooleanDataProvider
161
     */
162
    public function testHydrateBooleanPropertyWithInvalidValue(array $data): void
163
    {
164
        $object = new class {
165
            public bool $value;
166
        };
167
168
        $this->assertInvalidValueExceptionCount(1);
169
        $this->assertInvalidValueExceptionMessage(0, 'This value must be of type boolean.');
170
        $this->assertInvalidValueExceptionErrorCode(0, ErrorCode::MUST_BE_BOOLEAN);
171
        $this->assertInvalidValueExceptionPropertyPath(0, 'value');
172
        $this->createHydrator()->hydrate($object, $data);
173
    }
174
175
    /**
176
     * @group boolean
177
     */
178
    public function testHydrateBooleanPropertyWithoutValue(): void
179
    {
180
        $object = new class {
181
            public bool $value;
182
        };
183
184
        $this->assertInvalidValueExceptionCount(1);
185
        $this->assertInvalidValueExceptionMessage(0, 'This value must be provided.');
186
        $this->assertInvalidValueExceptionErrorCode(0, ErrorCode::MUST_BE_PROVIDED);
187
        $this->assertInvalidValueExceptionPropertyPath(0, 'value');
188
        $this->createHydrator()->hydrate($object, []);
189
    }
190
191
    /**
192
     * @group integer
193
     * @dataProvider strictIntegerDataProvider
194
     * @dataProvider nonStrictIntegerDataProvider
195
     */
196
    public function testHydrateIntegerProperty(array $data, int $expected): void
197
    {
198
        $object = new class {
199
            public int $value;
200
        };
201
202
        $this->assertInvalidValueExceptionCount(0);
203
        $this->createHydrator()->hydrate($object, $data);
204
        $this->assertSame($expected, $object->value);
205
    }
206
207
    /**
208
     * @group integer
209
     * @dataProvider strictIntegerDataProvider
210
     * @dataProvider nonStrictIntegerDataProvider
211
     * @dataProvider strictNullDataProvider
212
     * @dataProvider nonStrictNullDataProvider
213
     */
214
    public function testHydrateNullableIntegerProperty(array $data, ?int $expected): void
215
    {
216
        $object = new class {
217
            public ?int $value;
218
        };
219
220
        $this->assertInvalidValueExceptionCount(0);
221
        $this->createHydrator()->hydrate($object, $data);
222
        $this->assertSame($expected, $object->value);
223
    }
224
225
    /**
226
     * @group integer
227
     * @dataProvider strictIntegerDataProvider
228
     * @dataProvider nonStrictIntegerDataProvider
229
     * @dataProvider emptyArrayProvider
230
     */
231
    public function testHydrateOptionalIntegerProperty(array $data, int $expected = 42): void
232
    {
233
        $object = new class {
234
            public int $value = 42;
235
        };
236
237
        $this->assertInvalidValueExceptionCount(0);
238
        $this->createHydrator()->hydrate($object, $data);
239
        $this->assertSame($expected, $object->value);
240
    }
241
242
    /**
243
     * @group integer
244
     * @dataProvider strictNullDataProvider
245
     * @dataProvider nonStrictNullDataProvider
246
     */
247
    public function testHydrateIntegerPropertyWithEmptyValue(array $data): void
248
    {
249
        $object = new class {
250
            public int $value;
251
        };
252
253
        $this->assertInvalidValueExceptionCount(1);
254
        $this->assertInvalidValueExceptionMessage(0, 'This value must not be empty.');
255
        $this->assertInvalidValueExceptionErrorCode(0, ErrorCode::MUST_NOT_BE_EMPTY);
256
        $this->assertInvalidValueExceptionPropertyPath(0, 'value');
257
        $this->createHydrator()->hydrate($object, $data);
258
    }
259
260
    /**
261
     * @group integer
262
     * @dataProvider notIntegerDataProvider
263
     */
264
    public function testHydrateIntegerPropertyWithInvalidValue(array $data): void
265
    {
266
        $object = new class {
267
            public int $value;
268
        };
269
270
        $this->assertInvalidValueExceptionCount(1);
271
        $this->assertInvalidValueExceptionMessage(0, 'This value must be of type integer.');
272
        $this->assertInvalidValueExceptionErrorCode(0, ErrorCode::MUST_BE_INTEGER);
273
        $this->assertInvalidValueExceptionPropertyPath(0, 'value');
274
        $this->createHydrator()->hydrate($object, $data);
275
    }
276
277
    /**
278
     * @group integer
279
     */
280
    public function testHydrateIntegerPropertyWithoutValue(): void
281
    {
282
        $object = new class {
283
            public int $value;
284
        };
285
286
        $this->assertInvalidValueExceptionCount(1);
287
        $this->assertInvalidValueExceptionMessage(0, 'This value must be provided.');
288
        $this->assertInvalidValueExceptionErrorCode(0, ErrorCode::MUST_BE_PROVIDED);
289
        $this->assertInvalidValueExceptionPropertyPath(0, 'value');
290
        $this->createHydrator()->hydrate($object, []);
291
    }
292
293
    /**
294
     * @group number
295
     * @dataProvider strictNumberDataProvider
296
     * @dataProvider nonStrictNumberDataProvider
297
     */
298
    public function testHydrateNumericProperty(array $data, float $expected): void
299
    {
300
        $object = new class {
301
            public float $value;
302
        };
303
304
        $this->assertInvalidValueExceptionCount(0);
305
        $this->createHydrator()->hydrate($object, $data);
306
        $this->assertSame($expected, $object->value);
307
    }
308
309
    /**
310
     * @group number
311
     * @dataProvider strictNumberDataProvider
312
     * @dataProvider nonStrictNumberDataProvider
313
     * @dataProvider strictNullDataProvider
314
     * @dataProvider nonStrictNullDataProvider
315
     */
316
    public function testHydrateNullableNumericProperty(array $data, ?float $expected): void
317
    {
318
        $object = new class {
319
            public ?float $value;
320
        };
321
322
        $this->assertInvalidValueExceptionCount(0);
323
        $this->createHydrator()->hydrate($object, $data);
324
        $this->assertSame($expected, $object->value);
325
    }
326
327
    /**
328
     * @group number
329
     * @dataProvider strictNumberDataProvider
330
     * @dataProvider nonStrictNumberDataProvider
331
     * @dataProvider emptyArrayProvider
332
     */
333
    public function testHydrateOptionalNumericProperty(array $data, float $expected = 3.14159): void
334
    {
335
        $object = new class {
336
            public float $value = 3.14159;
337
        };
338
339
        $this->assertInvalidValueExceptionCount(0);
340
        $this->createHydrator()->hydrate($object, $data);
341
        $this->assertSame($expected, $object->value);
342
    }
343
344
    /**
345
     * @group number
346
     * @dataProvider strictNullDataProvider
347
     * @dataProvider nonStrictNullDataProvider
348
     */
349
    public function testHydrateNumericPropertyWithEmptyValue(array $data): void
350
    {
351
        $object = new class {
352
            public float $value;
353
        };
354
355
        $this->assertInvalidValueExceptionCount(1);
356
        $this->assertInvalidValueExceptionMessage(0, 'This value must not be empty.');
357
        $this->assertInvalidValueExceptionErrorCode(0, ErrorCode::MUST_NOT_BE_EMPTY);
358
        $this->assertInvalidValueExceptionPropertyPath(0, 'value');
359
        $this->createHydrator()->hydrate($object, $data);
360
    }
361
362
    /**
363
     * @group number
364
     * @dataProvider notNumberDataProvider
365
     */
366
    public function testHydrateNumericPropertyWithInvalidValue(array $data): void
367
    {
368
        $object = new class {
369
            public float $value;
370
        };
371
372
        $this->assertInvalidValueExceptionCount(1);
373
        $this->assertInvalidValueExceptionMessage(0, 'This value must be of type number.');
374
        $this->assertInvalidValueExceptionErrorCode(0, ErrorCode::MUST_BE_NUMBER);
375
        $this->assertInvalidValueExceptionPropertyPath(0, 'value');
376
        $this->createHydrator()->hydrate($object, $data);
377
    }
378
379
    /**
380
     * @group number
381
     */
382
    public function testHydrateNumericPropertyWithoutValue(): void
383
    {
384
        $object = new class {
385
            public float $value;
386
        };
387
388
        $this->assertInvalidValueExceptionCount(1);
389
        $this->assertInvalidValueExceptionMessage(0, 'This value must be provided.');
390
        $this->assertInvalidValueExceptionErrorCode(0, ErrorCode::MUST_BE_PROVIDED);
391
        $this->assertInvalidValueExceptionPropertyPath(0, 'value');
392
        $this->createHydrator()->hydrate($object, []);
393
    }
394
395
    /**
396
     * @group string
397
     * @dataProvider strictStringDataProvider
398
     * @dataProvider nonStrictStringDataProvider
399
     */
400
    public function testHydrateStringProperty(array $data, string $expected): void
401
    {
402
        $object = new class {
403
            public string $value;
404
        };
405
406
        $this->assertInvalidValueExceptionCount(0);
407
        $this->createHydrator()->hydrate($object, $data);
408
        $this->assertSame($expected, $object->value);
409
    }
410
411
    /**
412
     * @group string
413
     * @dataProvider strictStringDataProvider
414
     * @dataProvider nonStrictStringDataProvider
415
     * @dataProvider strictNullDataProvider
416
     */
417
    public function testHydrateNullableStringProperty(array $data, ?string $expected): void
418
    {
419
        $object = new class {
420
            public ?string $value;
421
        };
422
423
        $this->assertInvalidValueExceptionCount(0);
424
        $this->createHydrator()->hydrate($object, $data);
425
        $this->assertSame($expected, $object->value);
426
    }
427
428
    /**
429
     * @group string
430
     * @dataProvider strictStringDataProvider
431
     * @dataProvider nonStrictStringDataProvider
432
     * @dataProvider emptyArrayProvider
433
     */
434
    public function testHydrateOptionalStringProperty(array $data, string $expected = 'default'): void
435
    {
436
        $object = new class {
437
            public string $value = 'default';
438
        };
439
440
        $this->assertInvalidValueExceptionCount(0);
441
        $this->createHydrator()->hydrate($object, $data);
442
        $this->assertSame($expected, $object->value);
443
    }
444
445
    /**
446
     * @group string
447
     * @dataProvider strictNullDataProvider
448
     */
449
    public function testHydrateStringPropertyWithNull(array $data): void
450
    {
451
        $object = new class {
452
            public string $value;
453
        };
454
455
        $this->assertInvalidValueExceptionCount(1);
456
        $this->assertInvalidValueExceptionMessage(0, 'This value must not be empty.');
457
        $this->assertInvalidValueExceptionErrorCode(0, ErrorCode::MUST_NOT_BE_EMPTY);
458
        $this->assertInvalidValueExceptionPropertyPath(0, 'value');
459
        $this->createHydrator()->hydrate($object, $data);
460
    }
461
462
    /**
463
     * @group string
464
     * @dataProvider nonStrictNotStringDataProvider
465
     */
466
    public function testHydrateStringPropertyWithInvalidValue(array $data): void
467
    {
468
        $object = new class {
469
            public string $value;
470
        };
471
472
        $this->assertInvalidValueExceptionCount(1);
473
        $this->assertInvalidValueExceptionMessage(0, 'This value must be of type string.');
474
        $this->assertInvalidValueExceptionErrorCode(0, ErrorCode::MUST_BE_STRING);
475
        $this->assertInvalidValueExceptionPropertyPath(0, 'value');
476
        $this->createHydrator()->hydrate($object, $data);
477
    }
478
479
    /**
480
     * @group string
481
     */
482
    public function testHydrateStringPropertyWithoutValue(): void
483
    {
484
        $object = new class {
485
            public string $value;
486
        };
487
488
        $this->assertInvalidValueExceptionCount(1);
489
        $this->assertInvalidValueExceptionMessage(0, 'This value must be provided.');
490
        $this->assertInvalidValueExceptionErrorCode(0, ErrorCode::MUST_BE_PROVIDED);
491
        $this->assertInvalidValueExceptionPropertyPath(0, 'value');
492
        $this->createHydrator()->hydrate($object, []);
493
    }
494
495
    /**
496
     * @group enum
497
     * @group integer-enum
498
     * @dataProvider integerEnumDataProvider
499
     */
500
    public function testHydrateIntegerEnumProperty(array $data, $expected): void
501
    {
502
        $this->phpRequired('8.1');
503
504
        $object = new class {
505
            public Fixture\IntegerEnum $value;
506
        };
507
508
        $this->assertInvalidValueExceptionCount(0);
509
        $this->createHydrator()->hydrate($object, $data);
510
        $this->assertSame($expected, $object->value);
511
    }
512
513
    /**
514
     * @group enum
515
     * @group integer-enum
516
     * @dataProvider integerEnumDataProvider
517
     * @dataProvider strictNullDataProvider
518
     * @dataProvider nonStrictNullDataProvider
519
     */
520
    public function testHydrateNullableIntegerEnumProperty(array $data, $expected): void
521
    {
522
        $this->phpRequired('8.1');
523
524
        $object = new class {
525
            public ?Fixture\IntegerEnum $value;
526
        };
527
528
        $this->assertInvalidValueExceptionCount(0);
529
        $this->createHydrator()->hydrate($object, $data);
530
        $this->assertSame($expected, $object->value);
531
    }
532
533
    /**
534
     * @group enum
535
     * @group integer-enum
536
     * @dataProvider integerEnumDataProvider
537
     * @dataProvider emptyArrayProvider
538
     */
539
    public function testHydrateOptionalIntegerEnumProperty(array $data, $expected = null): void
540
    {
541
        $this->phpRequired('8.1');
542
543
        $object = new class {
544
            public ?Fixture\IntegerEnum $value = null;
545
        };
546
547
        $this->assertInvalidValueExceptionCount(0);
548
        $this->createHydrator()->hydrate($object, $data);
549
        $this->assertSame($expected, $object->value);
550
    }
551
552
    /**
553
     * @group enum
554
     * @group integer-enum
555
     * @dataProvider strictNullDataProvider
556
     * @dataProvider nonStrictNullDataProvider
557
     */
558
    public function testHydrateIntegerEnumPropertyWithEmptyValue(array $data): void
559
    {
560
        $this->phpRequired('8.1');
561
562
        $object = new class {
563
            public Fixture\IntegerEnum $value;
564
        };
565
566
        $this->assertInvalidValueExceptionCount(1);
567
        $this->assertInvalidValueExceptionMessage(0, 'This value must not be empty.');
568
        $this->assertInvalidValueExceptionErrorCode(0, ErrorCode::MUST_NOT_BE_EMPTY);
569
        $this->assertInvalidValueExceptionPropertyPath(0, 'value');
570
        $this->createHydrator()->hydrate($object, $data);
571
    }
572
573
    /**
574
     * @group enum
575
     * @group integer-enum
576
     * @dataProvider notIntegerDataProvider
577
     */
578
    public function testHydrateIntegerEnumPropertyWithInvalidValue(array $data): void
579
    {
580
        $this->phpRequired('8.1');
581
582
        $object = new class {
583
            public Fixture\IntegerEnum $value;
584
        };
585
586
        $this->assertInvalidValueExceptionCount(1);
587
        $this->assertInvalidValueExceptionMessage(0, 'This value must be of type integer.');
588
        $this->assertInvalidValueExceptionErrorCode(0, ErrorCode::MUST_BE_INTEGER);
589
        $this->assertInvalidValueExceptionPropertyPath(0, 'value');
590
        $this->createHydrator()->hydrate($object, $data);
591
    }
592
593
    /**
594
     * @group enum
595
     * @group integer-enum
596
     */
597
    public function testHydrateIntegerEnumPropertyWithUnknownValue(): void
598
    {
599
        $this->phpRequired('8.1');
600
601
        $object = new class {
602
            public Fixture\IntegerEnum $value;
603
        };
604
605
        $this->assertInvalidValueExceptionCount(1);
606
        // phpcs:ignore Generic.Files.LineLength
607
        $this->assertInvalidValueExceptionMessage(0, 'This value is not a valid choice; expected values: ' . join(', ', Fixture\IntegerEnum::values()) . '.');
608
        $this->assertInvalidValueExceptionErrorCode(0, ErrorCode::INVALID_CHOICE);
609
        $this->assertInvalidValueExceptionPropertyPath(0, 'value');
610
        $this->createHydrator()->hydrate($object, ['value' => 42]);
611
    }
612
613
    /**
614
     * @group enum
615
     * @group integer-enum
616
     */
617
    public function testHydrateIntegerEnumPropertyWithoutValue(): void
618
    {
619
        $this->phpRequired('8.1');
620
621
        $object = new class {
622
            public Fixture\IntegerEnum $value;
623
        };
624
625
        $this->assertInvalidValueExceptionCount(1);
626
        $this->assertInvalidValueExceptionMessage(0, 'This value must be provided.');
627
        $this->assertInvalidValueExceptionErrorCode(0, ErrorCode::MUST_BE_PROVIDED);
628
        $this->assertInvalidValueExceptionPropertyPath(0, 'value');
629
        $this->createHydrator()->hydrate($object, []);
630
    }
631
632
    /**
633
     * @group enum
634
     * @group string-enum
635
     * @dataProvider stringEnumDataProvider
636
     */
637
    public function testHydrateStringEnumProperty(array $data, $expected): void
638
    {
639
        $this->phpRequired('8.1');
640
641
        $object = new class {
642
            public Fixture\StringEnum $value;
643
        };
644
645
        $this->assertInvalidValueExceptionCount(0);
646
        $this->createHydrator()->hydrate($object, $data);
647
        $this->assertSame($expected, $object->value);
648
    }
649
650
    /**
651
     * @group enum
652
     * @group string-enum
653
     * @dataProvider stringEnumDataProvider
654
     * @dataProvider strictNullDataProvider
655
     * @dataProvider nonStrictNullDataProvider
656
     */
657
    public function testHydrateNullableStringEnumProperty(array $data, $expected): void
658
    {
659
        $this->phpRequired('8.1');
660
661
        $object = new class {
662
            public ?Fixture\StringEnum $value;
663
        };
664
665
        $this->assertInvalidValueExceptionCount(0);
666
        $this->createHydrator()->hydrate($object, $data);
667
        $this->assertSame($expected, $object->value);
668
    }
669
670
    /**
671
     * @group enum
672
     * @group string-enum
673
     * @dataProvider stringEnumDataProvider
674
     * @dataProvider emptyArrayProvider
675
     */
676
    public function testHydrateOptionalStringEnumProperty(array $data, $expected = null): void
677
    {
678
        $this->phpRequired('8.1');
679
680
        $object = new class {
681
            public ?Fixture\StringEnum $value = null;
682
        };
683
684
        $this->assertInvalidValueExceptionCount(0);
685
        $this->createHydrator()->hydrate($object, $data);
686
        $this->assertSame($expected, $object->value);
687
    }
688
689
    /**
690
     * @group enum
691
     * @group string-enum
692
     * @dataProvider strictNullDataProvider
693
     * @dataProvider nonStrictNullDataProvider
694
     */
695
    public function testHydrateStringEnumPropertyWithEmptyValue(array $data): void
696
    {
697
        $this->phpRequired('8.1');
698
699
        $object = new class {
700
            public Fixture\StringEnum $value;
701
        };
702
703
        $this->assertInvalidValueExceptionCount(1);
704
        $this->assertInvalidValueExceptionMessage(0, 'This value must not be empty.');
705
        $this->assertInvalidValueExceptionErrorCode(0, ErrorCode::MUST_NOT_BE_EMPTY);
706
        $this->assertInvalidValueExceptionPropertyPath(0, 'value');
707
        $this->createHydrator()->hydrate($object, $data);
708
    }
709
710
    /**
711
     * @group enum
712
     * @group string-enum
713
     * @dataProvider strictNotStringDataProvider
714
     */
715
    public function testHydrateStringEnumPropertyWithInvalidValue(array $data): void
716
    {
717
        $this->phpRequired('8.1');
718
719
        $object = new class {
720
            public Fixture\StringEnum $value;
721
        };
722
723
        $this->assertInvalidValueExceptionCount(1);
724
        $this->assertInvalidValueExceptionMessage(0, 'This value must be of type string.');
725
        $this->assertInvalidValueExceptionErrorCode(0, ErrorCode::MUST_BE_STRING);
726
        $this->assertInvalidValueExceptionPropertyPath(0, 'value');
727
        $this->createHydrator()->hydrate($object, $data);
728
    }
729
730
    /**
731
     * @group enum
732
     * @group string-enum
733
     */
734
    public function testHydrateStringEnumPropertyWithUnknownValue(): void
735
    {
736
        $this->phpRequired('8.1');
737
738
        $object = new class {
739
            public Fixture\StringEnum $value;
740
        };
741
742
        $this->assertInvalidValueExceptionCount(1);
743
        // phpcs:ignore Generic.Files.LineLength
744
        $this->assertInvalidValueExceptionMessage(0, 'This value is not a valid choice; expected values: ' . join(', ', Fixture\StringEnum::values()) . '.');
745
        $this->assertInvalidValueExceptionErrorCode(0, ErrorCode::INVALID_CHOICE);
746
        $this->assertInvalidValueExceptionPropertyPath(0, 'value');
747
        $this->createHydrator()->hydrate($object, ['value' => 'foo']);
748
    }
749
750
    /**
751
     * @group enum
752
     * @group string-enum
753
     */
754
    public function testHydrateStringEnumPropertyWithoutValue(): void
755
    {
756
        $this->phpRequired('8.1');
757
758
        $object = new class {
759
            public Fixture\StringEnum $value;
760
        };
761
762
        $this->assertInvalidValueExceptionCount(1);
763
        $this->assertInvalidValueExceptionMessage(0, 'This value must be provided.');
764
        $this->assertInvalidValueExceptionErrorCode(0, ErrorCode::MUST_BE_PROVIDED);
765
        $this->assertInvalidValueExceptionPropertyPath(0, 'value');
766
        $this->createHydrator()->hydrate($object, []);
767
    }
768
769
    /**
770
     * @group array
771
     * @dataProvider arrayDataProvider
772
     */
773
    public function testHydrateArrayProperty(array $data, array $expected): void
774
    {
775
        $object = new class {
776
            public array $value;
777
        };
778
779
        $this->assertInvalidValueExceptionCount(0);
780
        $this->createHydrator()->hydrate($object, $data);
781
        $this->assertSame($expected, $object->value);
782
    }
783
784
    /**
785
     * @group array
786
     * @dataProvider arrayDataProvider
787
     * @dataProvider strictNullDataProvider
788
     */
789
    public function testHydrateNullableArrayProperty(array $data, ?array $expected): void
790
    {
791
        $object = new class {
792
            public ?array $value;
793
        };
794
795
        $this->assertInvalidValueExceptionCount(0);
796
        $this->createHydrator()->hydrate($object, $data);
797
        $this->assertSame($expected, $object->value);
798
    }
799
800
    /**
801
     * @group array
802
     * @dataProvider arrayDataProvider
803
     * @dataProvider emptyArrayProvider
804
     */
805
    public function testHydrateOptionalArrayProperty(array $data, array $expected = []): void
806
    {
807
        $object = new class {
808
            public array $value = [];
809
        };
810
811
        $this->assertInvalidValueExceptionCount(0);
812
        $this->createHydrator()->hydrate($object, $data);
813
        $this->assertSame($expected, $object->value);
814
    }
815
816
    /**
817
     * @group array
818
     * @dataProvider strictNullDataProvider
819
     */
820
    public function testHydrateArrayPropertyWithNull(array $data): void
821
    {
822
        $object = new class {
823
            public array $value;
824
        };
825
826
        $this->assertInvalidValueExceptionCount(1);
827
        $this->assertInvalidValueExceptionMessage(0, 'This value must not be empty.');
828
        $this->assertInvalidValueExceptionErrorCode(0, ErrorCode::MUST_NOT_BE_EMPTY);
829
        $this->assertInvalidValueExceptionPropertyPath(0, 'value');
830
        $this->createHydrator()->hydrate($object, $data);
831
    }
832
833
    /**
834
     * @group array
835
     * @dataProvider notArrayDataProvider
836
     */
837
    public function testHydrateArrayPropertyWithInvalidValue(array $data): void
838
    {
839
        $object = new class {
840
            public array $value;
841
        };
842
843
        $this->assertInvalidValueExceptionCount(1);
844
        $this->assertInvalidValueExceptionMessage(0, 'This value must be of type array.');
845
        $this->assertInvalidValueExceptionErrorCode(0, ErrorCode::MUST_BE_ARRAY);
846
        $this->assertInvalidValueExceptionPropertyPath(0, 'value');
847
        $this->createHydrator()->hydrate($object, $data);
848
    }
849
850
    /**
851
     * @group array
852
     */
853
    public function testHydrateArrayPropertyWithoutValue(): void
854
    {
855
        $object = new class {
856
            public array $value;
857
        };
858
859
        $this->assertInvalidValueExceptionCount(1);
860
        $this->assertInvalidValueExceptionMessage(0, 'This value must be provided.');
861
        $this->assertInvalidValueExceptionErrorCode(0, ErrorCode::MUST_BE_PROVIDED);
862
        $this->assertInvalidValueExceptionPropertyPath(0, 'value');
863
        $this->createHydrator()->hydrate($object, []);
864
    }
865
866
    /**
867
     * @group array
868
     * @group boolean-array
869
     * @dataProvider strictBooleanProvider
870
     * @dataProvider nonStrictBooleanProvider
871
     */
872
    public function testHydrateBooleanArrayProperty($element, bool $expected): void
873
    {
874
        $object = new class {
875
            /** @Subtype(BuiltinType::BOOL) */
876
            #[Subtype(BuiltinType::BOOL)]
877
            public array $value;
878
        };
879
880
        $this->createHydrator()->hydrate($object, ['value' => [$element]]);
881
        $this->assertSame([$expected], $object->value);
882
    }
883
884
    /**
885
     * @group array
886
     * @group boolean-array
887
     * @dataProvider strictBooleanProvider
888
     * @dataProvider nonStrictBooleanProvider
889
     * @dataProvider strictNullProvider
890
     * @dataProvider nonStrictNullProvider
891
     */
892
    public function testHydrateNullableBooleanArrayProperty($element, ?bool $expected): void
893
    {
894
        $object = new class {
895
            /** @Subtype(BuiltinType::BOOL, allowsNull=true) */
896
            #[Subtype(BuiltinType::BOOL, allowsNull: true)]
897
            public array $value;
898
        };
899
900
        $this->createHydrator()->hydrate($object, ['value' => [$element]]);
901
        $this->assertSame([$expected], $object->value);
902
    }
903
904
    /**
905
     * @group array
906
     * @group boolean-array
907
     * @dataProvider strictNullProvider
908
     * @dataProvider nonStrictNullProvider
909
     */
910
    public function testHydrateBooleanArrayPropertyWithEmptyElement($element): void
911
    {
912
        $object = new class {
913
            /** @Subtype(BuiltinType::BOOL) */
914
            #[Subtype(BuiltinType::BOOL)]
915
            public array $value;
916
        };
917
918
        $this->assertInvalidValueExceptionCount(1);
919
        $this->assertInvalidValueExceptionMessage(0, 'This value must not be empty.');
920
        $this->assertInvalidValueExceptionErrorCode(0, ErrorCode::MUST_NOT_BE_EMPTY);
921
        $this->assertInvalidValueExceptionPropertyPath(0, 'value.0');
922
        $this->createHydrator()->hydrate($object, ['value' => [$element]]);
923
    }
924
925
    /**
926
     * @group array
927
     * @group boolean-array
928
     * @dataProvider notBooleanProvider
929
     */
930
    public function testHydrateBooleanArrayPropertyWithInvalidElement($element): void
931
    {
932
        $object = new class {
933
            /** @Subtype(BuiltinType::BOOL) */
934
            #[Subtype(BuiltinType::BOOL)]
935
            public array $value;
936
        };
937
938
        $this->assertInvalidValueExceptionCount(1);
939
        $this->assertInvalidValueExceptionMessage(0, 'This value must be of type boolean.');
940
        $this->assertInvalidValueExceptionErrorCode(0, ErrorCode::MUST_BE_BOOLEAN);
941
        $this->assertInvalidValueExceptionPropertyPath(0, 'value.0');
942
        $this->createHydrator()->hydrate($object, ['value' => [$element]]);
943
    }
944
945
    /**
946
     * @group array
947
     * @group boolean-array
948
     * @dataProvider strictBooleanProvider
949
     * @dataProvider nonStrictBooleanProvider
950
     */
951
    public function testHydrateLimitedBooleanArrayProperty($element): void
952
    {
953
        $object = new class {
954
            /** @Subtype(BuiltinType::BOOL, limit=1) */
955
            #[Subtype(BuiltinType::BOOL, limit: 1)]
956
            public array $value;
957
        };
958
959
        $this->assertInvalidValueExceptionCount(1);
960
        $this->assertInvalidValueExceptionMessage(0, 'This value is limited to 1 elements.');
961
        $this->assertInvalidValueExceptionErrorCode(0, ErrorCode::ARRAY_OVERFLOW);
962
        $this->assertInvalidValueExceptionPropertyPath(0, 'value');
963
        $this->createHydrator()->hydrate($object, ['value' => [true, $element]]);
964
    }
965
966
    /**
967
     * @group array
968
     * @group boolean-association-array
969
     * @dataProvider strictBooleanDataProvider
970
     * @dataProvider nonStrictBooleanDataProvider
971
     */
972
    public function testHydrateBooleanAssociationArrayProperty(array $data, bool $expected): void
973
    {
974
        $object = new class {
975
            /**
976
             * @Subtype(\Sunrise\Hydrator\Tests\Fixture\BooleanAssociation::class)
977
             * @var non-empty-list<Fixture\BooleanAssociation>
0 ignored issues
show
Documentation Bug introduced by
The doc comment non-empty-list<Fixture\BooleanAssociation> at position 0 could not be parsed: Unknown type name 'non-empty-list' at position 0 in non-empty-list<Fixture\BooleanAssociation>.
Loading history...
978
             */
979
            #[Subtype(Fixture\BooleanAssociation::class)]
980
            public array $value;
981
        };
982
983
        $this->createHydrator()->hydrate($object, ['value' => [$data]]);
984
        $this->assertSame($expected, $object->value[0]->value);
985
    }
986
987
    /**
988
     * @group array
989
     * @group boolean-association-array
990
     * @dataProvider strictNullDataProvider
991
     */
992
    public function testHydrateBooleanAssociationArrayPropertyWithNull(array $data): void
993
    {
994
        $object = new class {
995
            /**
996
             * @Subtype(\Sunrise\Hydrator\Tests\Fixture\BooleanAssociation::class)
997
             * @var non-empty-list<Fixture\BooleanAssociation>
0 ignored issues
show
Documentation Bug introduced by
The doc comment non-empty-list<Fixture\BooleanAssociation> at position 0 could not be parsed: Unknown type name 'non-empty-list' at position 0 in non-empty-list<Fixture\BooleanAssociation>.
Loading history...
998
             */
999
            #[Subtype(Fixture\BooleanAssociation::class)]
1000
            public array $value;
1001
        };
1002
1003
        $this->assertInvalidValueExceptionCount(1);
1004
        $this->assertInvalidValueExceptionMessage(0, 'This value must not be empty.');
1005
        $this->assertInvalidValueExceptionErrorCode(0, ErrorCode::MUST_NOT_BE_EMPTY);
1006
        $this->assertInvalidValueExceptionPropertyPath(0, 'value');
1007
        $this->createHydrator()->hydrate($object, $data);
1008
    }
1009
1010
    /**
1011
     * @group array
1012
     * @group boolean-association-array
1013
     * @dataProvider notArrayDataProvider
1014
     */
1015
    public function testHydrateBooleanAssociationArrayPropertyWithInvalidValue(array $data): void
1016
    {
1017
        $object = new class {
1018
            /**
1019
             * @Subtype(\Sunrise\Hydrator\Tests\Fixture\BooleanAssociation::class)
1020
             * @var non-empty-list<Fixture\BooleanAssociation>
0 ignored issues
show
Documentation Bug introduced by
The doc comment non-empty-list<Fixture\BooleanAssociation> at position 0 could not be parsed: Unknown type name 'non-empty-list' at position 0 in non-empty-list<Fixture\BooleanAssociation>.
Loading history...
1021
             */
1022
            #[Subtype(Fixture\BooleanAssociation::class)]
1023
            public array $value;
1024
        };
1025
1026
        $this->assertInvalidValueExceptionCount(1);
1027
        $this->assertInvalidValueExceptionMessage(0, 'This value must be of type array.');
1028
        $this->assertInvalidValueExceptionErrorCode(0, ErrorCode::MUST_BE_ARRAY);
1029
        $this->assertInvalidValueExceptionPropertyPath(0, 'value');
1030
        $this->createHydrator()->hydrate($object, $data);
1031
    }
1032
1033
    /**
1034
     * @group array
1035
     * @group boolean-association-array
1036
     */
1037
    public function testHydrateBooleanAssociationArrayPropertyWithoutValue(): void
1038
    {
1039
        $object = new class {
1040
            /**
1041
             * @Subtype(\Sunrise\Hydrator\Tests\Fixture\BooleanAssociation::class)
1042
             * @var non-empty-list<Fixture\BooleanAssociation>
0 ignored issues
show
Documentation Bug introduced by
The doc comment non-empty-list<Fixture\BooleanAssociation> at position 0 could not be parsed: Unknown type name 'non-empty-list' at position 0 in non-empty-list<Fixture\BooleanAssociation>.
Loading history...
1043
             */
1044
            #[Subtype(Fixture\BooleanAssociation::class)]
1045
            public array $value;
1046
        };
1047
1048
        $this->assertInvalidValueExceptionCount(1);
1049
        $this->assertInvalidValueExceptionMessage(0, 'This value must be provided.');
1050
        $this->assertInvalidValueExceptionErrorCode(0, ErrorCode::MUST_BE_PROVIDED);
1051
        $this->assertInvalidValueExceptionPropertyPath(0, 'value');
1052
        $this->createHydrator()->hydrate($object, []);
1053
    }
1054
1055
    /**
1056
     * @group array
1057
     * @group boolean-association-array
1058
     * @dataProvider notArrayProvider
1059
     */
1060
    public function testHydrateBooleanAssociationArrayPropertyWithInvalidAssociation($element): void
1061
    {
1062
        $object = new class {
1063
            /**
1064
             * @Subtype(\Sunrise\Hydrator\Tests\Fixture\BooleanAssociation::class)
1065
             * @var non-empty-list<Fixture\BooleanAssociation>
0 ignored issues
show
Documentation Bug introduced by
The doc comment non-empty-list<Fixture\BooleanAssociation> at position 0 could not be parsed: Unknown type name 'non-empty-list' at position 0 in non-empty-list<Fixture\BooleanAssociation>.
Loading history...
1066
             */
1067
            #[Subtype(Fixture\BooleanAssociation::class)]
1068
            public array $value;
1069
        };
1070
1071
        $this->assertInvalidValueExceptionCount(1);
1072
        $this->assertInvalidValueExceptionMessage(0, 'This value must be of type array.');
1073
        $this->assertInvalidValueExceptionErrorCode(0, ErrorCode::MUST_BE_ARRAY);
1074
        $this->assertInvalidValueExceptionPropertyPath(0, 'value.0');
1075
        $this->createHydrator()->hydrate($object, ['value' => [$element]]);
1076
    }
1077
1078
    /**
1079
     * @group array
1080
     * @group boolean-association-array
1081
     * @dataProvider strictNullDataProvider
1082
     * @dataProvider nonStrictNullDataProvider
1083
     */
1084
    public function testHydrateBooleanAssociationArrayPropertyWithEmptyAssociationValue(array $data): void
1085
    {
1086
        $object = new class {
1087
            /**
1088
             * @Subtype(\Sunrise\Hydrator\Tests\Fixture\BooleanAssociation::class)
1089
             * @var non-empty-list<Fixture\BooleanAssociation>
0 ignored issues
show
Documentation Bug introduced by
The doc comment non-empty-list<Fixture\BooleanAssociation> at position 0 could not be parsed: Unknown type name 'non-empty-list' at position 0 in non-empty-list<Fixture\BooleanAssociation>.
Loading history...
1090
             */
1091
            #[Subtype(Fixture\BooleanAssociation::class)]
1092
            public array $value;
1093
        };
1094
1095
        $this->assertInvalidValueExceptionCount(1);
1096
        $this->assertInvalidValueExceptionMessage(0, 'This value must not be empty.');
1097
        $this->assertInvalidValueExceptionErrorCode(0, ErrorCode::MUST_NOT_BE_EMPTY);
1098
        $this->assertInvalidValueExceptionPropertyPath(0, 'value.0.value');
1099
        $this->createHydrator()->hydrate($object, ['value' => [$data]]);
1100
    }
1101
1102
    /**
1103
     * @group array
1104
     * @group boolean-association-array
1105
     * @dataProvider notBooleanDataProvider
1106
     */
1107
    public function testHydrateBooleanAssociationArrayPropertyWithInvalidAssociationValue(array $data): void
1108
    {
1109
        $object = new class {
1110
            /**
1111
             * @Subtype(\Sunrise\Hydrator\Tests\Fixture\BooleanAssociation::class)
1112
             * @var non-empty-list<Fixture\BooleanAssociation>
0 ignored issues
show
Documentation Bug introduced by
The doc comment non-empty-list<Fixture\BooleanAssociation> at position 0 could not be parsed: Unknown type name 'non-empty-list' at position 0 in non-empty-list<Fixture\BooleanAssociation>.
Loading history...
1113
             */
1114
            #[Subtype(Fixture\BooleanAssociation::class)]
1115
            public array $value;
1116
        };
1117
1118
        $this->assertInvalidValueExceptionCount(1);
1119
        $this->assertInvalidValueExceptionMessage(0, 'This value must be of type boolean.');
1120
        $this->assertInvalidValueExceptionErrorCode(0, ErrorCode::MUST_BE_BOOLEAN);
1121
        $this->assertInvalidValueExceptionPropertyPath(0, 'value.0.value');
1122
        $this->createHydrator()->hydrate($object, ['value' => [$data]]);
1123
    }
1124
1125
    /**
1126
     * @group array-access
1127
     * @dataProvider arrayDataProvider
1128
     */
1129
    public function testHydrateArrayAccessProperty(array $data, array $expected): void
1130
    {
1131
        $object = new class {
1132
            public Fixture\Collection $value;
1133
        };
1134
1135
        $this->assertInvalidValueExceptionCount(0);
1136
        $this->createHydrator()->hydrate($object, $data);
1137
        $this->assertSame($expected, $object->value->elements);
1138
    }
1139
1140
    /**
1141
     * @group array-access
1142
     * @dataProvider arrayDataProvider
1143
     * @dataProvider strictNullDataProvider
1144
     */
1145
    public function testHydrateNullableArrayAccessProperty(array $data, ?array $expected): void
1146
    {
1147
        $object = new class {
1148
            public ?Fixture\Collection $value;
1149
        };
1150
1151
        $this->assertInvalidValueExceptionCount(0);
1152
        $this->createHydrator()->hydrate($object, $data);
1153
        $this->assertSame($expected, $object->value->elements ?? null);
1154
    }
1155
1156
    /**
1157
     * @group array-access
1158
     * @dataProvider arrayDataProvider
1159
     * @dataProvider emptyArrayProvider
1160
     */
1161
    public function testHydrateOptionalArrayAccessProperty(array $data, array $expected = []): void
1162
    {
1163
        $object = new class {
1164
            public ?Fixture\Collection $value = null;
1165
        };
1166
1167
        $this->assertInvalidValueExceptionCount(0);
1168
        $this->createHydrator()->hydrate($object, $data);
1169
        $this->assertSame($expected, $object->value->elements ?? []);
1170
    }
1171
1172
    /**
1173
     * @group array-access
1174
     * @dataProvider strictNullDataProvider
1175
     */
1176
    public function testHydrateArrayAccessPropertyWithNull(array $data): void
1177
    {
1178
        $object = new class {
1179
            public Fixture\Collection $value;
1180
        };
1181
1182
        $this->assertInvalidValueExceptionCount(1);
1183
        $this->assertInvalidValueExceptionMessage(0, 'This value must not be empty.');
1184
        $this->assertInvalidValueExceptionErrorCode(0, ErrorCode::MUST_NOT_BE_EMPTY);
1185
        $this->assertInvalidValueExceptionPropertyPath(0, 'value');
1186
        $this->createHydrator()->hydrate($object, $data);
1187
    }
1188
1189
    /**
1190
     * @group array-access
1191
     * @dataProvider notArrayDataProvider
1192
     */
1193
    public function testHydrateArrayAccessPropertyWithInvalidValue(array $data): void
1194
    {
1195
        $object = new class {
1196
            public Fixture\Collection $value;
1197
        };
1198
1199
        $this->assertInvalidValueExceptionCount(1);
1200
        $this->assertInvalidValueExceptionMessage(0, 'This value must be of type array.');
1201
        $this->assertInvalidValueExceptionErrorCode(0, ErrorCode::MUST_BE_ARRAY);
1202
        $this->assertInvalidValueExceptionPropertyPath(0, 'value');
1203
        $this->createHydrator()->hydrate($object, $data);
1204
    }
1205
1206
    /**
1207
     * @group array-access
1208
     */
1209
    public function testHydrateArrayAccessPropertyWithoutValue(): void
1210
    {
1211
        $object = new class {
1212
            public Fixture\Collection $value;
1213
        };
1214
1215
        $this->assertInvalidValueExceptionCount(1);
1216
        $this->assertInvalidValueExceptionMessage(0, 'This value must be provided.');
1217
        $this->assertInvalidValueExceptionErrorCode(0, ErrorCode::MUST_BE_PROVIDED);
1218
        $this->assertInvalidValueExceptionPropertyPath(0, 'value');
1219
        $this->createHydrator()->hydrate($object, []);
1220
    }
1221
1222
    /**
1223
     * @group array-access
1224
     */
1225
    public function testHydrateOverflowedArrayAccessProperty(): void
1226
    {
1227
        $object = new class {
1228
            public Fixture\LimitedCollection $value;
1229
        };
1230
1231
        $this->assertInvalidValueExceptionCount(1);
1232
        $this->assertInvalidValueExceptionMessage(0, 'This value is limited to 1 elements.');
1233
        $this->assertInvalidValueExceptionErrorCode(0, ErrorCode::ARRAY_OVERFLOW);
1234
        $this->assertInvalidValueExceptionPropertyPath(0, 'value');
1235
        $this->createHydrator()->hydrate($object, ['value' => ['foo', 'bar']]);
1236
    }
1237
1238
    /**
1239
     * @group array-access
1240
     */
1241
    public function testHydrateUnstantiableArrayAccessProperty(): void
1242
    {
1243
        $object = new class {
1244
            public Fixture\UnstantiableCollection $value;
1245
        };
1246
1247
        $this->expectException(InvalidObjectException::class);
1248
        $this->createHydrator()->hydrate($object, ['value' => []]);
1249
    }
1250
1251
    /**
1252
     * @group array-access
1253
     * @group annotated-boolean-array-access
1254
     * @dataProvider strictBooleanProvider
1255
     * @dataProvider nonStrictBooleanProvider
1256
     */
1257
    public function testHydrateAnnotatedBooleanArrayAccessProperty($element, bool $expected): void
1258
    {
1259
        $object = new class {
1260
            /** @Subtype(BuiltinType::BOOL) */
1261
            #[Subtype(BuiltinType::BOOL)]
1262
            public Fixture\Collection $value;
1263
        };
1264
1265
        $this->createHydrator()->hydrate($object, ['value' => [$element]]);
1266
        $this->assertSame([$expected], $object->value->elements);
1267
    }
1268
1269
    /**
1270
     * @group array-access
1271
     * @group annotated-boolean-array-access
1272
     * @dataProvider strictBooleanProvider
1273
     * @dataProvider nonStrictBooleanProvider
1274
     * @dataProvider strictNullProvider
1275
     * @dataProvider nonStrictNullProvider
1276
     */
1277
    public function testHydrateAnnotatedNullableBooleanArrayAccessProperty($element, ?bool $expected): void
1278
    {
1279
        $object = new class {
1280
            /** @Subtype(BuiltinType::BOOL, allowsNull=true) */
1281
            #[Subtype(BuiltinType::BOOL, allowsNull: true)]
1282
            public Fixture\Collection $value;
1283
        };
1284
1285
        $this->createHydrator()->hydrate($object, ['value' => [$element]]);
1286
        $this->assertSame([$expected], $object->value->elements);
1287
    }
1288
1289
    /**
1290
     * @group array-access
1291
     * @group annotated-boolean-array-access
1292
     * @dataProvider strictNullProvider
1293
     * @dataProvider nonStrictNullProvider
1294
     */
1295
    public function testHydrateAnnotatedBooleanArrayAccessPropertyWithEmptyElement($element): void
1296
    {
1297
        $object = new class {
1298
            /** @Subtype(BuiltinType::BOOL) */
1299
            #[Subtype(BuiltinType::BOOL)]
1300
            public Fixture\Collection $value;
1301
        };
1302
1303
        $this->assertInvalidValueExceptionCount(1);
1304
        $this->assertInvalidValueExceptionMessage(0, 'This value must not be empty.');
1305
        $this->assertInvalidValueExceptionErrorCode(0, ErrorCode::MUST_NOT_BE_EMPTY);
1306
        $this->assertInvalidValueExceptionPropertyPath(0, 'value.0');
1307
        $this->createHydrator()->hydrate($object, ['value' => [$element]]);
1308
    }
1309
1310
    /**
1311
     * @group array-access
1312
     * @group annotated-boolean-array-access
1313
     * @dataProvider notBooleanProvider
1314
     */
1315
    public function testHydrateAnnotatedBooleanArrayAccessPropertyWithInvalidElement($element): void
1316
    {
1317
        $object = new class {
1318
            /** @Subtype(BuiltinType::BOOL) */
1319
            #[Subtype(BuiltinType::BOOL)]
1320
            public Fixture\Collection $value;
1321
        };
1322
1323
        $this->assertInvalidValueExceptionCount(1);
1324
        $this->assertInvalidValueExceptionMessage(0, 'This value must be of type boolean.');
1325
        $this->assertInvalidValueExceptionErrorCode(0, ErrorCode::MUST_BE_BOOLEAN);
1326
        $this->assertInvalidValueExceptionPropertyPath(0, 'value.0');
1327
        $this->createHydrator()->hydrate($object, ['value' => [$element]]);
1328
    }
1329
1330
    /**
1331
     * @group array-access
1332
     * @group annotated-boolean-array-access
1333
     * @dataProvider strictBooleanProvider
1334
     * @dataProvider nonStrictBooleanProvider
1335
     */
1336
    public function testHydrateAnnotatedLimitedBooleanArrayAccessProperty($element): void
1337
    {
1338
        $object = new class {
1339
            /** @Subtype(BuiltinType::BOOL, limit=1) */
1340
            #[Subtype(BuiltinType::BOOL, limit: 1)]
1341
            public Fixture\Collection $value;
1342
        };
1343
1344
        $this->assertInvalidValueExceptionCount(1);
1345
        $this->assertInvalidValueExceptionMessage(0, 'This value is limited to 1 elements.');
1346
        $this->assertInvalidValueExceptionErrorCode(0, ErrorCode::ARRAY_OVERFLOW);
1347
        $this->assertInvalidValueExceptionPropertyPath(0, 'value');
1348
        $this->createHydrator()->hydrate($object, ['value' => [true, $element]]);
1349
    }
1350
1351
    /**
1352
     * @group array-access
1353
     * @group annotated-boolean-array-access
1354
     * @dataProvider strictBooleanProvider
1355
     * @dataProvider nonStrictBooleanProvider
1356
     */
1357
    public function testHydrateAnnotatedOverflowedBooleanArrayAccessProperty($element): void
1358
    {
1359
        $object = new class {
1360
            /** @Subtype(BuiltinType::BOOL) */
1361
            #[Subtype(BuiltinType::BOOL)]
1362
            public Fixture\LimitedCollection $value;
1363
        };
1364
1365
        $this->assertInvalidValueExceptionCount(1);
1366
        $this->assertInvalidValueExceptionMessage(0, 'This value is limited to 1 elements.');
1367
        $this->assertInvalidValueExceptionErrorCode(0, ErrorCode::ARRAY_OVERFLOW);
1368
        $this->assertInvalidValueExceptionPropertyPath(0, 'value');
1369
        $this->createHydrator()->hydrate($object, ['value' => [true, $element]]);
1370
    }
1371
1372
    /**
1373
     * @group array-access
1374
     * @group typed-boolean-array-access
1375
     * @dataProvider strictBooleanProvider
1376
     * @dataProvider nonStrictBooleanProvider
1377
     */
1378
    public function testHydrateTypedBooleanArrayAccessProperty($element, bool $expected): void
1379
    {
1380
        $object = new class {
1381
            public Fixture\BooleanCollection $value;
1382
        };
1383
1384
        $this->createHydrator()->hydrate($object, ['value' => [$element]]);
1385
        $this->assertSame([$expected], $object->value->elements);
1386
    }
1387
1388
    /**
1389
     * @group array-access
1390
     * @group typed-boolean-array-access
1391
     * @dataProvider strictBooleanProvider
1392
     * @dataProvider nonStrictBooleanProvider
1393
     * @dataProvider strictNullProvider
1394
     * @dataProvider nonStrictNullProvider
1395
     */
1396
    public function testHydrateTypedNullableBooleanArrayAccessProperty($element, ?bool $expected): void
1397
    {
1398
        $object = new class {
1399
            public Fixture\NullableBooleanCollection $value;
1400
        };
1401
1402
        $this->createHydrator()->hydrate($object, ['value' => [$element]]);
1403
        $this->assertSame([$expected], $object->value->elements);
1404
    }
1405
1406
    /**
1407
     * @group array-access
1408
     * @group typed-boolean-array-access
1409
     * @dataProvider strictNullProvider
1410
     * @dataProvider nonStrictNullProvider
1411
     */
1412
    public function testHydrateTypedBooleanArrayAccessPropertyWithEmptyElement($element): void
1413
    {
1414
        $object = new class {
1415
            public Fixture\BooleanCollection $value;
1416
        };
1417
1418
        $this->assertInvalidValueExceptionCount(1);
1419
        $this->assertInvalidValueExceptionMessage(0, 'This value must not be empty.');
1420
        $this->assertInvalidValueExceptionErrorCode(0, ErrorCode::MUST_NOT_BE_EMPTY);
1421
        $this->assertInvalidValueExceptionPropertyPath(0, 'value.0');
1422
        $this->createHydrator()->hydrate($object, ['value' => [$element]]);
1423
    }
1424
1425
    /**
1426
     * @group array-access
1427
     * @group typed-boolean-array-access
1428
     * @dataProvider notBooleanProvider
1429
     */
1430
    public function testHydrateTypedBooleanArrayAccessPropertyWithInvalidElement($element): void
1431
    {
1432
        $object = new class {
1433
            public Fixture\BooleanCollection $value;
1434
        };
1435
1436
        $this->assertInvalidValueExceptionCount(1);
1437
        $this->assertInvalidValueExceptionMessage(0, 'This value must be of type boolean.');
1438
        $this->assertInvalidValueExceptionErrorCode(0, ErrorCode::MUST_BE_BOOLEAN);
1439
        $this->assertInvalidValueExceptionPropertyPath(0, 'value.0');
1440
        $this->createHydrator()->hydrate($object, ['value' => [$element]]);
1441
    }
1442
1443
    /**
1444
     * @group array-access
1445
     * @group typed-boolean-array-access
1446
     * @dataProvider strictBooleanProvider
1447
     * @dataProvider nonStrictBooleanProvider
1448
     */
1449
    public function testHydrateTypedLimitedBooleanArrayAccessProperty($element): void
1450
    {
1451
        $object = new class {
1452
            public Fixture\LimitedCollection $value;
1453
        };
1454
1455
        $this->assertInvalidValueExceptionCount(1);
1456
        $this->assertInvalidValueExceptionMessage(0, 'This value is limited to 1 elements.');
1457
        $this->assertInvalidValueExceptionErrorCode(0, ErrorCode::ARRAY_OVERFLOW);
1458
        $this->assertInvalidValueExceptionPropertyPath(0, 'value');
1459
        $this->createHydrator()->hydrate($object, ['value' => [true, $element]]);
1460
    }
1461
1462
    /**
1463
     * @group array-access
1464
     * @group typed-boolean-array-access
1465
     * @dataProvider strictBooleanProvider
1466
     * @dataProvider nonStrictBooleanProvider
1467
     */
1468
    public function testHydrateTypedOverflowedBooleanArrayAccessProperty($element): void
1469
    {
1470
        $object = new class {
1471
            public Fixture\LimitedBooleanCollection $value;
1472
        };
1473
1474
        $this->assertInvalidValueExceptionCount(1);
1475
        $this->assertInvalidValueExceptionMessage(0, 'This value is limited to 1 elements.');
1476
        $this->assertInvalidValueExceptionErrorCode(0, ErrorCode::ARRAY_OVERFLOW);
1477
        $this->assertInvalidValueExceptionPropertyPath(0, 'value');
1478
        $this->createHydrator()->hydrate($object, ['value' => [true, $element]]);
1479
    }
1480
1481
    /**
1482
     * @group array-access
1483
     * @group boolean-association-array-access
1484
     * @dataProvider strictBooleanDataProvider
1485
     * @dataProvider nonStrictBooleanDataProvider
1486
     */
1487
    public function testHydrateBooleanAssociationArrayAccessProperty(array $data, bool $expected): void
1488
    {
1489
        $object = new class {
1490
            public Fixture\BooleanAssociationCollection $value;
1491
        };
1492
1493
        $this->createHydrator()->hydrate($object, ['value' => [$data]]);
1494
        $this->assertSame($expected, $object->value[0]->value);
1495
    }
1496
1497
    /**
1498
     * @group array-access
1499
     * @group boolean-association-array-access
1500
     * @dataProvider strictNullDataProvider
1501
     */
1502
    public function testHydrateBooleanAssociationArrayAccessPropertyWithNull(array $data): void
1503
    {
1504
        $object = new class {
1505
            public Fixture\BooleanAssociationCollection $value;
1506
        };
1507
1508
        $this->assertInvalidValueExceptionCount(1);
1509
        $this->assertInvalidValueExceptionMessage(0, 'This value must not be empty.');
1510
        $this->assertInvalidValueExceptionErrorCode(0, ErrorCode::MUST_NOT_BE_EMPTY);
1511
        $this->assertInvalidValueExceptionPropertyPath(0, 'value');
1512
        $this->createHydrator()->hydrate($object, $data);
1513
    }
1514
1515
    /**
1516
     * @group array-access
1517
     * @group boolean-association-array-access
1518
     * @dataProvider notArrayDataProvider
1519
     */
1520
    public function testHydrateBooleanAssociationArrayAccessPropertyWithInvalidValue(array $data): void
1521
    {
1522
        $object = new class {
1523
            public Fixture\BooleanAssociationCollection $value;
1524
        };
1525
1526
        $this->assertInvalidValueExceptionCount(1);
1527
        $this->assertInvalidValueExceptionMessage(0, 'This value must be of type array.');
1528
        $this->assertInvalidValueExceptionErrorCode(0, ErrorCode::MUST_BE_ARRAY);
1529
        $this->assertInvalidValueExceptionPropertyPath(0, 'value');
1530
        $this->createHydrator()->hydrate($object, $data);
1531
    }
1532
1533
    /**
1534
     * @group array-access
1535
     * @group boolean-association-array-access
1536
     */
1537
    public function testHydrateBooleanAssociationArrayAccessPropertyWithoutValue(): void
1538
    {
1539
        $object = new class {
1540
            public Fixture\BooleanAssociationCollection $value;
1541
        };
1542
1543
        $this->assertInvalidValueExceptionCount(1);
1544
        $this->assertInvalidValueExceptionMessage(0, 'This value must be provided.');
1545
        $this->assertInvalidValueExceptionErrorCode(0, ErrorCode::MUST_BE_PROVIDED);
1546
        $this->assertInvalidValueExceptionPropertyPath(0, 'value');
1547
        $this->createHydrator()->hydrate($object, []);
1548
    }
1549
1550
    /**
1551
     * @group array-access
1552
     * @group boolean-association-array-access
1553
     * @dataProvider notArrayProvider
1554
     */
1555
    public function testHydrateBooleanAssociationArrayAccessPropertyWithInvalidAssociation($actual): void
1556
    {
1557
        $object = new class {
1558
            public Fixture\BooleanAssociationCollection $value;
1559
        };
1560
1561
        $this->assertInvalidValueExceptionCount(1);
1562
        $this->assertInvalidValueExceptionMessage(0, 'This value must be of type array.');
1563
        $this->assertInvalidValueExceptionErrorCode(0, ErrorCode::MUST_BE_ARRAY);
1564
        $this->assertInvalidValueExceptionPropertyPath(0, 'value.0');
1565
        $this->createHydrator()->hydrate($object, ['value' => [$actual]]);
1566
    }
1567
1568
    /**
1569
     * @group array-access
1570
     * @group boolean-association-array-access
1571
     * @dataProvider strictNullDataProvider
1572
     * @dataProvider nonStrictNullDataProvider
1573
     */
1574
    public function testHydrateBooleanAssociationArrayAccessPropertyWithEmptyAssociationValue(array $data): void
1575
    {
1576
        $object = new class {
1577
            public Fixture\BooleanAssociationCollection $value;
1578
        };
1579
1580
        $this->assertInvalidValueExceptionCount(1);
1581
        $this->assertInvalidValueExceptionMessage(0, 'This value must not be empty.');
1582
        $this->assertInvalidValueExceptionErrorCode(0, ErrorCode::MUST_NOT_BE_EMPTY);
1583
        $this->assertInvalidValueExceptionPropertyPath(0, 'value.0.value');
1584
        $this->createHydrator()->hydrate($object, ['value' => [$data]]);
1585
    }
1586
1587
    /**
1588
     * @group array-access
1589
     * @group boolean-association-array-access
1590
     * @dataProvider notBooleanDataProvider
1591
     */
1592
    public function testHydrateBooleanAssociationArrayAccessPropertyWithInvalidAssociationValue(array $data): void
1593
    {
1594
        $object = new class {
1595
            public Fixture\BooleanAssociationCollection $value;
1596
        };
1597
1598
        $this->assertInvalidValueExceptionCount(1);
1599
        $this->assertInvalidValueExceptionMessage(0, 'This value must be of type boolean.');
1600
        $this->assertInvalidValueExceptionErrorCode(0, ErrorCode::MUST_BE_BOOLEAN);
1601
        $this->assertInvalidValueExceptionPropertyPath(0, 'value.0.value');
1602
        $this->createHydrator()->hydrate($object, ['value' => [$data]]);
1603
    }
1604
1605
    /**
1606
     * @group association
1607
     */
1608
    public function testHydrateUnstantiableAssociationProperty(): void
1609
    {
1610
        $object = new class {
1611
            public Fixture\UnstantiableObject $value;
1612
        };
1613
1614
        $this->expectException(InvalidObjectException::class);
1615
        $this->createHydrator()->hydrate($object, ['value' => 'foo']);
1616
    }
1617
1618
    /**
1619
     * @group association
1620
     * @group boolean-association
1621
     * @dataProvider strictBooleanDataProvider
1622
     * @dataProvider nonStrictBooleanDataProvider
1623
     */
1624
    public function testHydrateBooleanAssociationProperty(array $data, bool $expected): void
1625
    {
1626
        $object = new class {
1627
            public Fixture\BooleanAssociation $value;
1628
        };
1629
1630
        $this->assertInvalidValueExceptionCount(0);
1631
        $this->createHydrator()->hydrate($object, ['value' => $data]);
1632
        $this->assertSame($expected, $object->value->value);
1633
    }
1634
1635
    /**
1636
     * @group association
1637
     * @group boolean-association
1638
     */
1639
    public function testHydrateNullableBooleanAssociationProperty(): void
1640
    {
1641
        $object = new class {
1642
            public ?Fixture\BooleanAssociation $value;
1643
        };
1644
1645
        $this->assertInvalidValueExceptionCount(0);
1646
        $this->createHydrator()->hydrate($object, ['value' => null]);
1647
        $this->assertNull($object->value);
1648
    }
1649
1650
    /**
1651
     * @group association
1652
     * @group boolean-association
1653
     */
1654
    public function testHydrateOptionalBooleanAssociationProperty(): void
1655
    {
1656
        $object = new class {
1657
            public ?Fixture\BooleanAssociation $value = null;
1658
        };
1659
1660
        $this->assertInvalidValueExceptionCount(0);
1661
        $this->createHydrator()->hydrate($object, []);
1662
        $this->assertNull($object->value);
1663
    }
1664
1665
    /**
1666
     * @group association
1667
     * @group boolean-association
1668
     */
1669
    public function testHydrateBooleanAssociationPropertyWithNull(): void
1670
    {
1671
        $object = new class {
1672
            public Fixture\BooleanAssociation $value;
1673
        };
1674
1675
        $this->assertInvalidValueExceptionCount(1);
1676
        $this->assertInvalidValueExceptionMessage(0, 'This value must not be empty.');
1677
        $this->assertInvalidValueExceptionErrorCode(0, ErrorCode::MUST_NOT_BE_EMPTY);
1678
        $this->assertInvalidValueExceptionPropertyPath(0, 'value');
1679
        $this->createHydrator()->hydrate($object, ['value' => null]);
1680
    }
1681
1682
    /**
1683
     * @group association
1684
     * @group boolean-association
1685
     * @dataProvider notArrayDataProvider
1686
     */
1687
    public function testHydrateBooleanAssociationPropertyWithInvalidValue(array $data): void
1688
    {
1689
        $object = new class {
1690
            public Fixture\BooleanAssociation $value;
1691
        };
1692
1693
        $this->assertInvalidValueExceptionCount(1);
1694
        $this->assertInvalidValueExceptionMessage(0, 'This value must be of type array.');
1695
        $this->assertInvalidValueExceptionErrorCode(0, ErrorCode::MUST_BE_ARRAY);
1696
        $this->assertInvalidValueExceptionPropertyPath(0, 'value');
1697
        $this->createHydrator()->hydrate($object, $data);
1698
    }
1699
1700
    /**
1701
     * @group association
1702
     * @group boolean-association
1703
     */
1704
    public function testHydrateBooleanAssociationPropertyWithoutValue(): void
1705
    {
1706
        $object = new class {
1707
            public Fixture\BooleanAssociation $value;
1708
        };
1709
1710
        $this->assertInvalidValueExceptionCount(1);
1711
        $this->assertInvalidValueExceptionMessage(0, 'This value must be provided.');
1712
        $this->assertInvalidValueExceptionErrorCode(0, ErrorCode::MUST_BE_PROVIDED);
1713
        $this->assertInvalidValueExceptionPropertyPath(0, 'value');
1714
        $this->createHydrator()->hydrate($object, []);
1715
    }
1716
1717
    /**
1718
     * @group association
1719
     * @group boolean-association
1720
     */
1721
    public function testHydrateBooleanAssociationPropertyWithEmptyAssociation(): void
1722
    {
1723
        $object = new class {
1724
            public Fixture\BooleanAssociation $value;
1725
        };
1726
1727
        $this->assertInvalidValueExceptionCount(1);
1728
        $this->assertInvalidValueExceptionMessage(0, 'This value must be provided.');
1729
        $this->assertInvalidValueExceptionErrorCode(0, ErrorCode::MUST_BE_PROVIDED);
1730
        $this->assertInvalidValueExceptionPropertyPath(0, 'value.value');
1731
        $this->createHydrator()->hydrate($object, ['value' => []]);
1732
    }
1733
1734
    /**
1735
     * @group association
1736
     * @group boolean-association
1737
     * @dataProvider notBooleanDataProvider
1738
     */
1739
    public function testHydrateBooleanAssociationPropertyWithInvalidAssociationValue(array $data): void
1740
    {
1741
        $object = new class {
1742
            public Fixture\BooleanAssociation $value;
1743
        };
1744
1745
        $this->assertInvalidValueExceptionCount(1);
1746
        $this->assertInvalidValueExceptionMessage(0, 'This value must be of type boolean.');
1747
        $this->assertInvalidValueExceptionErrorCode(0, ErrorCode::MUST_BE_BOOLEAN);
1748
        $this->assertInvalidValueExceptionPropertyPath(0, 'value.value');
1749
        $this->createHydrator()->hydrate($object, ['value' => $data]);
1750
    }
1751
1752
    /**
1753
     * @group timestamp
1754
     * @dataProvider timestampDataProvider
1755
     */
1756
    // phpcs:ignore Generic.Files.LineLength
1757
    public function testHydrateTimestampProperty(array $data, string $expected, ?string $format = null, ?string $timezone = null): void
1758
    {
1759
        $object = new class {
1760
            public DateTimeImmutable $value;
1761
        };
1762
1763
        $this->assertInvalidValueExceptionCount(0);
1764
        // phpcs:ignore Generic.Files.LineLength
1765
        $this->createHydrator([ContextKey::TIMESTAMP_FORMAT => $format, ContextKey::TIMEZONE => $timezone])->hydrate($object, $data);
1766
        $this->assertSame($expected, $object->value->format($format ?? TimestampTypeConverter::DEFAULT_FORMAT));
1767
    }
1768
1769
    /**
1770
     * @group timestamp
1771
     * @dataProvider timestampDataProvider
1772
     * @dataProvider strictNullDataProvider
1773
     * @dataProvider nonStrictNullDataProvider
1774
     */
1775
    // phpcs:ignore Generic.Files.LineLength
1776
    public function testHydrateNullableTimestampProperty(array $data, ?string $expected, ?string $format = null, ?string $timezone = null): void
1777
    {
1778
        $object = new class {
1779
            public ?DateTimeImmutable $value;
1780
        };
1781
1782
        $this->assertInvalidValueExceptionCount(0);
1783
        // phpcs:ignore Generic.Files.LineLength
1784
        $this->createHydrator([ContextKey::TIMESTAMP_FORMAT => $format, ContextKey::TIMEZONE => $timezone])->hydrate($object, $data);
1785
        // phpcs:ignore Generic.Files.LineLength
1786
        $this->assertSame($expected, isset($object->value) ? $object->value->format($format ?? TimestampTypeConverter::DEFAULT_FORMAT) : null);
0 ignored issues
show
Bug introduced by
The method format() does not exist on null. ( Ignorable by Annotation )

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

1786
        $this->assertSame($expected, isset($object->value) ? $object->value->/** @scrutinizer ignore-call */ format($format ?? TimestampTypeConverter::DEFAULT_FORMAT) : null);

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
1787
    }
1788
1789
    /**
1790
     * @group timestamp
1791
     * @dataProvider timestampDataProvider
1792
     * @dataProvider emptyArrayProvider
1793
     */
1794
    // phpcs:ignore Generic.Files.LineLength
1795
    public function testHydrateOptionalTimestampProperty(array $data, ?string $expected = null, ?string $format = null, ?string $timezone = null): void
1796
    {
1797
        $object = new class {
1798
            public ?DateTimeImmutable $value = null;
1799
        };
1800
1801
        $this->assertInvalidValueExceptionCount(0);
1802
        // phpcs:ignore Generic.Files.LineLength
1803
        $this->createHydrator([ContextKey::TIMESTAMP_FORMAT => $format, ContextKey::TIMEZONE => $timezone])->hydrate($object, $data);
1804
        // phpcs:ignore Generic.Files.LineLength
1805
        $this->assertSame($expected, isset($object->value) ? $object->value->format($format ?? TimestampTypeConverter::DEFAULT_FORMAT) : null);
1806
    }
1807
1808
    /**
1809
     * @group timestamp
1810
     * @dataProvider strictNullDataProvider
1811
     * @dataProvider nonStrictNullDataProvider
1812
     */
1813
    public function testHydrateTimestampPropertyWithEmptyValue(array $data): void
1814
    {
1815
        $object = new class {
1816
            public DateTimeImmutable $value;
1817
        };
1818
1819
        $this->assertInvalidValueExceptionCount(1);
1820
        $this->assertInvalidValueExceptionMessage(0, 'This value must not be empty.');
1821
        $this->assertInvalidValueExceptionErrorCode(0, ErrorCode::MUST_NOT_BE_EMPTY);
1822
        $this->assertInvalidValueExceptionPropertyPath(0, 'value');
1823
        $this->createHydrator()->hydrate($object, $data);
1824
    }
1825
1826
    /**
1827
     * @group timestamp
1828
     * @dataProvider invalidTimestampDataProvider
1829
     */
1830
    // phpcs:ignore Generic.Files.LineLength
1831
    public function testHydrateTimestampPropertyWithInvalidValue(array $data, ?string $format, string $errorCode, string $errorMessage): void
1832
    {
1833
        $object = new class {
1834
            public DateTimeImmutable $value;
1835
        };
1836
1837
        $this->assertInvalidValueExceptionCount(1);
1838
        $this->assertInvalidValueExceptionMessage(0, $errorMessage);
1839
        $this->assertInvalidValueExceptionErrorCode(0, $errorCode);
1840
        $this->assertInvalidValueExceptionPropertyPath(0, 'value');
1841
        $this->createHydrator([ContextKey::TIMESTAMP_FORMAT => $format])->hydrate($object, $data);
1842
    }
1843
1844
    /**
1845
     * @group timestamp
1846
     */
1847
    public function testHydrateTimestampPropertyWithoutValue(): void
1848
    {
1849
        $object = new class {
1850
            public DateTimeImmutable $value;
1851
        };
1852
1853
        $this->assertInvalidValueExceptionCount(1);
1854
        $this->assertInvalidValueExceptionMessage(0, 'This value must be provided.');
1855
        $this->assertInvalidValueExceptionErrorCode(0, ErrorCode::MUST_BE_PROVIDED);
1856
        $this->assertInvalidValueExceptionPropertyPath(0, 'value');
1857
        $this->createHydrator()->hydrate($object, []);
1858
    }
1859
1860
    /**
1861
     * @group timestamp
1862
     * @dataProvider timestampDataProvider
1863
     */
1864
    // phpcs:ignore Generic.Files.LineLength
1865
    public function testHydrateOverriddenDateTimeImmutable(array $data, string $expected, ?string $format = null, ?string $timezone = null): void
1866
    {
1867
        $object = new class {
1868
            public Fixture\OverriddenDateTimeImmutable $value;
1869
        };
1870
1871
        $this->assertInvalidValueExceptionCount(0);
1872
        // phpcs:ignore Generic.Files.LineLength
1873
        $this->createHydrator([ContextKey::TIMESTAMP_FORMAT => $format, ContextKey::TIMEZONE => $timezone])->hydrate($object, $data);
1874
        $this->assertSame($expected, $object->value->format($format ?? TimestampTypeConverter::DEFAULT_FORMAT));
1875
    }
1876
1877
    /**
1878
     * @group timezone
1879
     * @dataProvider timezoneDataProvider
1880
     */
1881
    public function testHydrateTimezoneProperty(array $data, string $expected): void
1882
    {
1883
        $object = new class {
1884
            public DateTimeZone $value;
1885
        };
1886
1887
        $this->assertInvalidValueExceptionCount(0);
1888
        $this->createHydrator()->hydrate($object, $data);
1889
        $this->assertSame($expected, $object->value->getName());
1890
    }
1891
1892
    /**
1893
     * @group timezone
1894
     * @dataProvider timezoneDataProvider
1895
     * @dataProvider strictNullDataProvider
1896
     * @dataProvider nonStrictNullDataProvider
1897
     */
1898
    public function testHydrateNullableTimezoneProperty(array $data, ?string $expected): void
1899
    {
1900
        $object = new class {
1901
            public ?DateTimeZone $value;
1902
        };
1903
1904
        $this->assertInvalidValueExceptionCount(0);
1905
        $this->createHydrator()->hydrate($object, $data);
1906
        $this->assertSame($expected, isset($object->value) ? $object->value->getName() : null);
1907
    }
1908
1909
    /**
1910
     * @group timezone
1911
     * @dataProvider timezoneDataProvider
1912
     * @dataProvider emptyArrayProvider
1913
     */
1914
    public function testHydrateOptionalTimezoneProperty(array $data, ?string $expected = null): void
1915
    {
1916
        $object = new class {
1917
            public ?DateTimeZone $value = null;
1918
        };
1919
1920
        $this->assertInvalidValueExceptionCount(0);
1921
        $this->createHydrator()->hydrate($object, $data);
1922
        $this->assertSame($expected, isset($object->value) ? $object->value->getName() : null);
1923
    }
1924
1925
    /**
1926
     * @group timezone
1927
     * @dataProvider strictNullDataProvider
1928
     * @dataProvider nonStrictNullDataProvider
1929
     */
1930
    public function testHydrateTimezonePropertyWithEmptyValue(array $data): void
1931
    {
1932
        $object = new class {
1933
            public DateTimeZone $value;
1934
        };
1935
1936
        $this->assertInvalidValueExceptionCount(1);
1937
        $this->assertInvalidValueExceptionMessage(0, 'This value must not be empty.');
1938
        $this->assertInvalidValueExceptionErrorCode(0, ErrorCode::MUST_NOT_BE_EMPTY);
1939
        $this->assertInvalidValueExceptionPropertyPath(0, 'value');
1940
        $this->createHydrator()->hydrate($object, $data);
1941
    }
1942
1943
    /**
1944
     * @group timezone
1945
     * @dataProvider strictNotStringDataProvider
1946
     */
1947
    public function testHydrateTimezonePropertyWithInvalidValue(array $data): void
1948
    {
1949
        $object = new class {
1950
            public DateTimeZone $value;
1951
        };
1952
1953
        $this->assertInvalidValueExceptionCount(1);
1954
        $this->assertInvalidValueExceptionMessage(0, 'This value must be of type string.');
1955
        $this->assertInvalidValueExceptionErrorCode(0, ErrorCode::MUST_BE_STRING);
1956
        $this->assertInvalidValueExceptionPropertyPath(0, 'value');
1957
        $this->createHydrator()->hydrate($object, $data);
1958
    }
1959
1960
    /**
1961
     * @group timezone
1962
     */
1963
    public function testHydrateTimezonePropertyWithUnknownValue(): void
1964
    {
1965
        $object = new class {
1966
            public DateTimeZone $value;
1967
        };
1968
1969
        $this->assertInvalidValueExceptionCount(1);
1970
        $this->assertInvalidValueExceptionMessage(0, 'This value is not a valid timezone.');
1971
        $this->assertInvalidValueExceptionErrorCode(0, ErrorCode::INVALID_TIMEZONE);
1972
        $this->assertInvalidValueExceptionPropertyPath(0, 'value');
1973
        $this->createHydrator()->hydrate($object, ['value' => 'Jupiter/Europa']);
1974
    }
1975
1976
    /**
1977
     * @group timezone
1978
     */
1979
    public function testHydrateTimezonePropertyWithoutValue(): void
1980
    {
1981
        $object = new class {
1982
            public DateTimeZone $value;
1983
        };
1984
1985
        $this->assertInvalidValueExceptionCount(1);
1986
        $this->assertInvalidValueExceptionMessage(0, 'This value must be provided.');
1987
        $this->assertInvalidValueExceptionErrorCode(0, ErrorCode::MUST_BE_PROVIDED);
1988
        $this->assertInvalidValueExceptionPropertyPath(0, 'value');
1989
        $this->createHydrator()->hydrate($object, []);
1990
    }
1991
1992
    /**
1993
     * @group myclabs-enum
1994
     * @dataProvider myclabsEnumDataProvider
1995
     */
1996
    public function testHydrateMyclabsEnumProperty(array $data, $expected): void
1997
    {
1998
        $object = new class {
1999
            public Fixture\MyclabsEnum $value;
2000
        };
2001
2002
        $this->assertInvalidValueExceptionCount(0);
2003
        $this->createHydrator()->hydrate($object, $data);
2004
        $this->assertEquals($expected, $object->value);
2005
    }
2006
2007
    /**
2008
     * @group myclabs-enum
2009
     * @dataProvider myclabsEnumDataProvider
2010
     * @dataProvider strictNullDataProvider
2011
     * @dataProvider nonStrictNullDataProvider
2012
     */
2013
    public function testHydrateNullableMyclabsEnumProperty(array $data, $expected): void
2014
    {
2015
        $object = new class {
2016
            public ?Fixture\MyclabsEnum $value;
2017
        };
2018
2019
        $this->assertInvalidValueExceptionCount(0);
2020
        $this->createHydrator()->hydrate($object, $data);
2021
        $this->assertEquals($expected, $object->value);
2022
    }
2023
2024
    /**
2025
     * @group myclabs-enum
2026
     * @dataProvider myclabsEnumDataProvider
2027
     * @dataProvider emptyArrayProvider
2028
     */
2029
    public function testHydrateOptionalMyclabsEnumProperty(array $data, $expected = null): void
2030
    {
2031
        $object = new class {
2032
            public ?Fixture\MyclabsEnum $value = null;
2033
        };
2034
2035
        $this->assertInvalidValueExceptionCount(0);
2036
        $this->createHydrator()->hydrate($object, $data);
2037
        $this->assertEquals($expected, $object->value);
2038
    }
2039
2040
    /**
2041
     * @group myclabs-enum
2042
     * @dataProvider strictNullDataProvider
2043
     * @dataProvider nonStrictNullDataProvider
2044
     */
2045
    public function testHydrateMyclabsEnumPropertyWithEmptyValue(array $data): void
2046
    {
2047
        $object = new class {
2048
            public Fixture\MyclabsEnum $value;
2049
        };
2050
2051
        $this->assertInvalidValueExceptionCount(1);
2052
        $this->assertInvalidValueExceptionMessage(0, 'This value must not be empty.');
2053
        $this->assertInvalidValueExceptionErrorCode(0, ErrorCode::MUST_NOT_BE_EMPTY);
2054
        $this->assertInvalidValueExceptionPropertyPath(0, 'value');
2055
        $this->createHydrator()->hydrate($object, $data);
2056
    }
2057
2058
    /**
2059
     * @group myclabs-enum
2060
     */
2061
    public function testHydrateMyclabsEnumPropertyWithUnknownValue(): void
2062
    {
2063
        $object = new class {
2064
            public Fixture\MyclabsEnum $value;
2065
        };
2066
2067
        $this->assertInvalidValueExceptionCount(1);
2068
        // phpcs:ignore Generic.Files.LineLength
2069
        $this->assertInvalidValueExceptionMessage(0, 'This value is not a valid choice; expected values: ' . join(', ', Fixture\MyclabsEnum::toArray()) . '.');
2070
        $this->assertInvalidValueExceptionErrorCode(0, ErrorCode::INVALID_CHOICE);
2071
        $this->assertInvalidValueExceptionPropertyPath(0, 'value');
2072
        $this->createHydrator()->hydrate($object, ['value' => 'foo']);
2073
    }
2074
2075
    /**
2076
     * @group myclabs-enum
2077
     */
2078
    public function testHydrateMyclabsEnumPropertyWithoutValue(): void
2079
    {
2080
        $object = new class {
2081
            public Fixture\MyclabsEnum $value;
2082
        };
2083
2084
        $this->assertInvalidValueExceptionCount(1);
2085
        $this->assertInvalidValueExceptionMessage(0, 'This value must be provided.');
2086
        $this->assertInvalidValueExceptionErrorCode(0, ErrorCode::MUST_BE_PROVIDED);
2087
        $this->assertInvalidValueExceptionPropertyPath(0, 'value');
2088
        $this->createHydrator()->hydrate($object, []);
2089
    }
2090
2091
    /**
2092
     * @group ramsey-uuid
2093
     * @dataProvider uuidDataProvider
2094
     */
2095
    public function testHydrateRamseyUuidProperty(array $data, string $expected): void
2096
    {
2097
        $object = new class {
2098
            public \Ramsey\Uuid\UuidInterface $value;
2099
        };
2100
2101
        $this->assertInvalidValueExceptionCount(0);
2102
        $this->createHydrator()->hydrate($object, $data);
2103
        $this->assertSame($expected, $object->value->toString());
2104
    }
2105
2106
    /**
2107
     * @group ramsey-uuid
2108
     * @dataProvider uuidDataProvider
2109
     * @dataProvider strictNullDataProvider
2110
     * @dataProvider nonStrictNullDataProvider
2111
     */
2112
    public function testHydrateNullableRamseyUuidProperty(array $data, ?string $expected): void
2113
    {
2114
        $object = new class {
2115
            public ?\Ramsey\Uuid\UuidInterface $value;
2116
        };
2117
2118
        $this->assertInvalidValueExceptionCount(0);
2119
        $this->createHydrator()->hydrate($object, $data);
2120
        $this->assertSame($expected, isset($object->value) ? $object->value->toString() : null);
2121
    }
2122
2123
    /**
2124
     * @group ramsey-uuid
2125
     * @dataProvider uuidDataProvider
2126
     * @dataProvider emptyArrayProvider
2127
     */
2128
    public function testHydrateOptionalRamseyUuidProperty(array $data, ?string $expected = null): void
2129
    {
2130
        $object = new class {
2131
            public ?\Ramsey\Uuid\UuidInterface $value = null;
2132
        };
2133
2134
        $this->assertInvalidValueExceptionCount(0);
2135
        $this->createHydrator()->hydrate($object, $data);
2136
        $this->assertSame($expected, isset($object->value) ? $object->value->toString() : null);
2137
    }
2138
2139
    /**
2140
     * @group ramsey-uuid
2141
     * @dataProvider strictNullDataProvider
2142
     * @dataProvider nonStrictNullDataProvider
2143
     */
2144
    public function testHydrateRamseyUuidPropertyWithEmptyValue(array $data): void
2145
    {
2146
        $object = new class {
2147
            public \Ramsey\Uuid\UuidInterface $value;
2148
        };
2149
2150
        $this->assertInvalidValueExceptionCount(1);
2151
        $this->assertInvalidValueExceptionMessage(0, 'This value must not be empty.');
2152
        $this->assertInvalidValueExceptionErrorCode(0, ErrorCode::MUST_NOT_BE_EMPTY);
2153
        $this->assertInvalidValueExceptionPropertyPath(0, 'value');
2154
        $this->createHydrator()->hydrate($object, $data);
2155
    }
2156
2157
    /**
2158
     * @group ramsey-uuid
2159
     * @dataProvider strictNotStringDataProvider
2160
     */
2161
    public function testHydrateRamseyUuidPropertyWithInvalidValue(array $data): void
2162
    {
2163
        $object = new class {
2164
            public \Ramsey\Uuid\UuidInterface $value;
2165
        };
2166
2167
        $this->assertInvalidValueExceptionCount(1);
2168
        $this->assertInvalidValueExceptionMessage(0, 'This value must be of type string.');
2169
        $this->assertInvalidValueExceptionErrorCode(0, ErrorCode::MUST_BE_STRING);
2170
        $this->assertInvalidValueExceptionPropertyPath(0, 'value');
2171
        $this->createHydrator()->hydrate($object, $data);
2172
    }
2173
2174
    /**
2175
     * @group ramsey-uuid
2176
     */
2177
    public function testHydrateRamseyUuidPropertyWithInvalidUuid(): void
2178
    {
2179
        $object = new class {
2180
            public \Ramsey\Uuid\UuidInterface $value;
2181
        };
2182
2183
        $this->assertInvalidValueExceptionCount(1);
2184
        $this->assertInvalidValueExceptionMessage(0, 'This value is not a valid UID.');
2185
        $this->assertInvalidValueExceptionErrorCode(0, ErrorCode::INVALID_UID);
2186
        $this->assertInvalidValueExceptionPropertyPath(0, 'value');
2187
        $this->createHydrator()->hydrate($object, ['value' => 'foo']);
2188
    }
2189
2190
    /**
2191
     * @group ramsey-uuid
2192
     */
2193
    public function testHydrateRamseyUuidPropertyWithoutValue(): void
2194
    {
2195
        $object = new class {
2196
            public \Ramsey\Uuid\UuidInterface $value;
2197
        };
2198
2199
        $this->assertInvalidValueExceptionCount(1);
2200
        $this->assertInvalidValueExceptionMessage(0, 'This value must be provided.');
2201
        $this->assertInvalidValueExceptionErrorCode(0, ErrorCode::MUST_BE_PROVIDED);
2202
        $this->assertInvalidValueExceptionPropertyPath(0, 'value');
2203
        $this->createHydrator()->hydrate($object, []);
2204
    }
2205
2206
    /**
2207
     * @group symfony-uuid
2208
     * @dataProvider uuidDataProvider
2209
     */
2210
    public function testHydrateSymfonyUuidProperty(array $data, string $expected): void
2211
    {
2212
        $object = new class {
2213
            public \Symfony\Component\Uid\UuidV4 $value;
2214
        };
2215
2216
        $this->assertInvalidValueExceptionCount(0);
2217
        $this->createHydrator()->hydrate($object, $data);
2218
        $this->assertSame($expected, $object->value->toRfc4122());
2219
    }
2220
2221
    /**
2222
     * @group symfony-uuid
2223
     * @dataProvider uuidDataProvider
2224
     * @dataProvider strictNullDataProvider
2225
     * @dataProvider nonStrictNullDataProvider
2226
     */
2227
    public function testHydrateNullableSymfonyUuidProperty(array $data, ?string $expected): void
2228
    {
2229
        $object = new class {
2230
            public ?\Symfony\Component\Uid\UuidV4 $value;
2231
        };
2232
2233
        $this->assertInvalidValueExceptionCount(0);
2234
        $this->createHydrator()->hydrate($object, $data);
2235
        $this->assertSame($expected, isset($object->value) ? $object->value->toRfc4122() : null);
2236
    }
2237
2238
    /**
2239
     * @group symfony-uuid
2240
     * @dataProvider uuidDataProvider
2241
     * @dataProvider emptyArrayProvider
2242
     */
2243
    public function testHydrateOptionalSymfonyUuidProperty(array $data, ?string $expected = null): void
2244
    {
2245
        $object = new class {
2246
            public ?\Symfony\Component\Uid\UuidV4 $value = null;
2247
        };
2248
2249
        $this->assertInvalidValueExceptionCount(0);
2250
        $this->createHydrator()->hydrate($object, $data);
2251
        $this->assertSame($expected, isset($object->value) ? $object->value->toRfc4122() : null);
2252
    }
2253
2254
    /**
2255
     * @group symfony-uuid
2256
     * @dataProvider strictNullDataProvider
2257
     * @dataProvider nonStrictNullDataProvider
2258
     */
2259
    public function testHydrateSymfonyUuidPropertyWithEmptyValue(array $data): void
2260
    {
2261
        $object = new class {
2262
            public \Symfony\Component\Uid\UuidV4 $value;
2263
        };
2264
2265
        $this->assertInvalidValueExceptionCount(1);
2266
        $this->assertInvalidValueExceptionMessage(0, 'This value must not be empty.');
2267
        $this->assertInvalidValueExceptionErrorCode(0, ErrorCode::MUST_NOT_BE_EMPTY);
2268
        $this->assertInvalidValueExceptionPropertyPath(0, 'value');
2269
        $this->createHydrator()->hydrate($object, $data);
2270
    }
2271
2272
    /**
2273
     * @group symfony-uuid
2274
     * @dataProvider strictNotStringDataProvider
2275
     */
2276
    public function testHydrateSymfonyUuidPropertyWithInvalidValue(array $data): void
2277
    {
2278
        $object = new class {
2279
            public \Symfony\Component\Uid\UuidV4 $value;
2280
        };
2281
2282
        $this->assertInvalidValueExceptionCount(1);
2283
        $this->assertInvalidValueExceptionMessage(0, 'This value must be of type string.');
2284
        $this->assertInvalidValueExceptionErrorCode(0, ErrorCode::MUST_BE_STRING);
2285
        $this->assertInvalidValueExceptionPropertyPath(0, 'value');
2286
        $this->createHydrator()->hydrate($object, $data);
2287
    }
2288
2289
    /**
2290
     * @group symfony-uuid
2291
     */
2292
    public function testHydrateSymfonyUuidPropertyWithInvalidUuid(): void
2293
    {
2294
        $object = new class {
2295
            public \Symfony\Component\Uid\UuidV4 $value;
2296
        };
2297
2298
        $this->assertInvalidValueExceptionCount(1);
2299
        $this->assertInvalidValueExceptionMessage(0, 'This value is not a valid UID.');
2300
        $this->assertInvalidValueExceptionErrorCode(0, ErrorCode::INVALID_UID);
2301
        $this->assertInvalidValueExceptionPropertyPath(0, 'value');
2302
        $this->createHydrator()->hydrate($object, ['value' => 'foo']);
2303
    }
2304
2305
    /**
2306
     * @group symfony-uuid
2307
     */
2308
    public function testHydrateSymfonyUuidPropertyWithoutValue(): void
2309
    {
2310
        $object = new class {
2311
            public \Symfony\Component\Uid\UuidV4 $value;
2312
        };
2313
2314
        $this->assertInvalidValueExceptionCount(1);
2315
        $this->assertInvalidValueExceptionMessage(0, 'This value must be provided.');
2316
        $this->assertInvalidValueExceptionErrorCode(0, ErrorCode::MUST_BE_PROVIDED);
2317
        $this->assertInvalidValueExceptionPropertyPath(0, 'value');
2318
        $this->createHydrator()->hydrate($object, []);
2319
    }
2320
2321
    /**
2322
     * @group json
2323
     */
2324
    public function testHydrateObjectWithJson(): void
2325
    {
2326
        $object = new class {
2327
            public string $value;
2328
        };
2329
2330
        $this->assertInvalidValueExceptionCount(0);
2331
        $this->createHydrator()->hydrateWithJson($object, '{"value": "foo"}');
2332
        $this->assertSame('foo', $object->value);
2333
    }
2334
2335
    /**
2336
     * @group json
2337
     */
2338
    public function testHydrateObjectWithInvalidJson(): void
2339
    {
2340
        $object = new class {
2341
            public string $value;
2342
        };
2343
2344
        $this->expectException(InvalidDataException::class);
2345
        $this->expectExceptionMessageMatches('/^The JSON is invalid and couldn‘t be decoded due to: .+$/');
2346
        $this->createHydrator()->hydrateWithJson($object, '[[]]', 0, 1);
2347
    }
2348
2349
    /**
2350
     * @group json
2351
     */
2352
    public function testHydrateObjectWithNonObjectableJson(): void
2353
    {
2354
        $object = new class {
2355
            public string $value;
2356
        };
2357
2358
        $this->expectException(InvalidDataException::class);
2359
        $this->expectExceptionMessage('The JSON must be in the form of an array or an object.');
2360
        $this->createHydrator()->hydrateWithJson($object, 'null');
2361
    }
2362
2363
    public function testInvalidObjectExceptionUnsupportedMethodParameterType(): void
2364
    {
2365
        $class = $this->createMock(ReflectionClass::class);
2366
        $class->method('getName')->willReturn('foo');
2367
2368
        $method = $this->createMock(ReflectionMethod::class);
2369
        $method->method('getDeclaringClass')->willReturn($class);
2370
        $method->method('getName')->willReturn('bar');
2371
2372
        $parameter = $this->createMock(ReflectionParameter::class);
2373
        $parameter->method('getDeclaringClass')->willReturn($class);
2374
        $parameter->method('getDeclaringFunction')->willReturn($method);
2375
        $parameter->method('getName')->willReturn('baz');
2376
2377
        $type = new Type($parameter, 'mixed', false);
2378
2379
        $e = InvalidObjectException::unsupportedParameterType($type, $parameter);
2380
        // phpcs:ignore Generic.Files.LineLength
2381
        $this->assertSame('The parameter {foo::bar($baz[0])} is associated with an unsupported type {mixed}.', $e->getMessage());
2382
    }
2383
2384
    public function testInvalidObjectExceptionUnsupportedFunctionParameterType(): void
2385
    {
2386
        $function = $this->createMock(ReflectionFunction::class);
2387
        $function->method('getName')->willReturn('foo');
2388
2389
        $parameter = $this->createMock(ReflectionParameter::class);
2390
        $parameter->method('getDeclaringFunction')->willReturn($function);
2391
        $parameter->method('getName')->willReturn('bar');
2392
2393
        $type = new Type($parameter, 'mixed', false);
2394
2395
        $e = InvalidObjectException::unsupportedParameterType($type, $parameter);
2396
        // phpcs:ignore Generic.Files.LineLength
2397
        $this->assertSame('The parameter {foo($bar[0])} is associated with an unsupported type {mixed}.', $e->getMessage());
2398
    }
2399
2400
    public function testTypeFromParameter(): void
2401
    {
2402
        $parameter = $this->createMock(ReflectionParameter::class);
2403
        $parameter->method('getType')->willReturn(null);
2404
        $type = Type::fromParameter($parameter);
2405
        $this->assertSame(BuiltinType::MIXED, $type->getName());
2406
        $this->assertTrue($type->allowsNull());
2407
2408
        $namedType = $this->createMock(ReflectionNamedType::class);
2409
        $namedType->method('getName')->willReturn('foo');
2410
        $namedType->method('allowsNull')->willReturn(false);
2411
        $parameter = $this->createMock(ReflectionParameter::class);
2412
        $parameter->method('getType')->willReturn($namedType);
2413
        $type = Type::fromParameter($parameter);
2414
        $this->assertSame('foo', $type->getName());
2415
        $this->assertFalse($type->allowsNull());
2416
2417
        $namedType = $this->createMock(ReflectionNamedType::class);
2418
        $namedType->method('getName')->willReturn('foo');
2419
        $namedType->method('allowsNull')->willReturn(true);
2420
        $parameter = $this->createMock(ReflectionParameter::class);
2421
        $parameter->method('getType')->willReturn($namedType);
2422
        $type = Type::fromParameter($parameter);
2423
        $this->assertSame('foo', $type->getName());
2424
        $this->assertTrue($type->allowsNull());
2425
2426
        if (PHP_VERSION_ID < 80000) {
2427
            return;
2428
        }
2429
2430
        $namedTypes = [];
2431
        $namedTypes[0] = $this->createMock(ReflectionNamedType::class);
2432
        $namedTypes[0]->method('getName')->willReturn('foo');
2433
        $namedTypes[1] = $this->createMock(ReflectionNamedType::class);
2434
        $namedTypes[1]->method('getName')->willReturn('bar');
2435
        $unionType = $this->createMock(ReflectionUnionType::class);
2436
        $unionType->method('getTypes')->willReturn($namedTypes);
2437
        $unionType->method('allowsNull')->willReturn(false);
2438
        $unionType->method('__toString')->willReturn('foo|bar');
2439
        $parameter = $this->createMock(ReflectionParameter::class);
2440
        $parameter->method('getType')->willReturn($unionType);
2441
        $type = Type::fromParameter($parameter);
2442
        $this->assertSame('foo|bar', $type->getName());
2443
        $this->assertFalse($type->allowsNull());
2444
2445
        $namedTypes = [];
2446
        $namedTypes[0] = $this->createMock(ReflectionNamedType::class);
2447
        $namedTypes[0]->method('getName')->willReturn('foo');
2448
        $namedTypes[1] = $this->createMock(ReflectionNamedType::class);
2449
        $namedTypes[1]->method('getName')->willReturn('bar');
2450
        $unionType = $this->createMock(ReflectionUnionType::class);
2451
        $unionType->method('getTypes')->willReturn($namedTypes);
2452
        $unionType->method('allowsNull')->willReturn(true);
2453
        $unionType->method('__toString')->willReturn('foo|bar|null');
2454
        $parameter = $this->createMock(ReflectionParameter::class);
2455
        $parameter->method('getType')->willReturn($unionType);
2456
        $type = Type::fromParameter($parameter);
2457
        $this->assertSame('foo|bar|null', $type->getName());
2458
        $this->assertTrue($type->allowsNull());
2459
2460
        if (PHP_VERSION_ID < 80100) {
2461
            return;
2462
        }
2463
2464
        $namedTypes = [];
2465
        $namedTypes[0] = $this->createMock(ReflectionNamedType::class);
2466
        $namedTypes[0]->method('getName')->willReturn('foo');
2467
        $namedTypes[1] = $this->createMock(ReflectionNamedType::class);
2468
        $namedTypes[1]->method('getName')->willReturn('bar');
2469
        $intersectionType = $this->createMock(ReflectionIntersectionType::class);
2470
        $intersectionType->method('getTypes')->willReturn($namedTypes);
2471
        $intersectionType->method('allowsNull')->willReturn(false);
2472
        $intersectionType->method('__toString')->willReturn('foo&bar');
2473
        $parameter = $this->createMock(ReflectionParameter::class);
2474
        $parameter->method('getType')->willReturn($intersectionType);
2475
        $type = Type::fromParameter($parameter);
2476
        $this->assertSame('foo&bar', $type->getName());
2477
        $this->assertFalse($type->allowsNull());
2478
2479
        $namedTypes = [];
2480
        $namedTypes[0] = $this->createMock(ReflectionNamedType::class);
2481
        $namedTypes[0]->method('getName')->willReturn('foo');
2482
        $namedTypes[1] = $this->createMock(ReflectionNamedType::class);
2483
        $namedTypes[1]->method('getName')->willReturn('bar');
2484
        $intersectionType = $this->createMock(ReflectionIntersectionType::class);
2485
        $intersectionType->method('getTypes')->willReturn($namedTypes);
2486
        $intersectionType->method('allowsNull')->willReturn(true);
2487
        $intersectionType->method('__toString')->willReturn('(foo&bar)|null');
2488
        $parameter = $this->createMock(ReflectionParameter::class);
2489
        $parameter->method('getType')->willReturn($intersectionType);
2490
        $type = Type::fromParameter($parameter);
2491
        $this->assertSame('(foo&bar)|null', $type->getName());
2492
        $this->assertTrue($type->allowsNull());
2493
    }
2494
2495
    public function testHydrateStore(): void
2496
    {
2497
        $this->phpRequired('8.1');
2498
2499
        $sold = Fixture\Store\Status::SOLD;
2500
2501
        $data = [
2502
            'name' => 'Pear',
2503
            'category' => [
2504
                'name' => 'Vegetables',
2505
            ],
2506
            'tags' => [
2507
                [
2508
                    'name' => 'foo',
2509
                ],
2510
                [
2511
                    'name' => 'bar',
2512
                ],
2513
            ],
2514
            'status' => $sold->value,
2515
        ];
2516
2517
        $this->assertInvalidValueExceptionCount(0);
2518
        $product = $this->createHydrator()->hydrate(Fixture\Store\Product::class, $data);
2519
        $this->assertSame('Pear', $product->name);
2520
        $this->assertSame('Vegetables', $product->category->name);
2521
        $this->assertCount(2, $product->tags);
2522
        $this->assertArrayHasKey(0, $product->tags);
2523
        $this->assertSame('foo', $product->tags[0]->name);
2524
        $this->assertArrayHasKey(1, $product->tags);
2525
        $this->assertSame('bar', $product->tags[1]->name);
2526
        $this->assertSame($sold, $product->status);
2527
        $this->assertSame('2020-01-01 12:00:00', $product->createdAt->format('Y-m-d H:i:s'));
2528
    }
2529
2530
    public function testUnknownObject(): void
2531
    {
2532
        $this->expectException(InvalidObjectException::class);
2533
        $this->createHydrator()->hydrate(\Unknown::class, []);
0 ignored issues
show
Bug introduced by
The type Unknown was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
2534
    }
2535
2536
    public function testUnstantiableObject(): void
2537
    {
2538
        $this->expectException(InvalidObjectException::class);
2539
        $this->createHydrator()->hydrate(Fixture\UnstantiableObject::class, []);
2540
    }
2541
2542
    public function testStaticalProperty(): void
2543
    {
2544
        $object = new class {
2545
            public static string $value = 'foo';
2546
        };
2547
2548
        $this->assertInvalidValueExceptionCount(0);
2549
        $this->createHydrator()->hydrate($object, ['value' => 'bar']);
2550
        $this->assertSame('foo', $object::$value);
2551
    }
2552
2553
    public function testIgnoredProperty(): void
2554
    {
2555
        $object = new class {
2556
            /** @Ignore() */
2557
            #[Ignore]
2558
            public string $value = 'foo';
2559
        };
2560
2561
        $this->assertInvalidValueExceptionCount(0);
2562
        $this->createHydrator()->hydrate($object, ['value' => 'bar']);
2563
        $this->assertSame('foo', $object->value);
2564
    }
2565
2566
    public function testAliasedProperty(): void
2567
    {
2568
        $proto = new class {
2569
            /** @Alias("alias") */
2570
            #[Alias('alias')]
2571
            public string $value;
2572
        };
2573
2574
        $this->assertInvalidValueExceptionCount(0);
2575
        $object = $this->createHydrator()->hydrate(get_class($proto), ['alias' => 'foo']);
2576
        $this->assertSame('foo', $object->value);
2577
2578
        $this->assertInvalidValueExceptionCount(1);
2579
        $this->createHydrator()->hydrate(get_class($proto), ['value' => 'foo']);
2580
        $this->assertInvalidValueExceptionMessage(0, 'This value must be provided.');
2581
        $this->assertInvalidValueExceptionErrorCode(0, ErrorCode::MUST_BE_PROVIDED);
2582
        $this->assertInvalidValueExceptionPropertyPath(0, 'value');
2583
    }
2584
2585
    public function testUntypedProperty(): void
2586
    {
2587
        $object = new class {
2588
            public $value;
2589
        };
2590
2591
        $this->createHydrator()->hydrate($object, ['value' => 'foo']);
2592
        $this->assertSame('foo', $object->value);
2593
    }
2594
2595
    public function testUnsupportedPropertyType(): void
2596
    {
2597
        $object = new class {
2598
            public balalaika $value;
0 ignored issues
show
Bug introduced by
The type Sunrise\Hydrator\Tests\balalaika was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
2599
        };
2600
2601
        $this->expectException(InvalidObjectException::class);
2602
        $this->createHydrator()->hydrate($object, ['value' => 'foo']);
2603
    }
2604
2605
    public function testSymfonyViolations(): void
2606
    {
2607
        $violations = null;
2608
2609
        try {
2610
            $this->createHydrator()->hydrate(new class {
2611
                public string $value;
2612
            }, []);
2613
        } catch (InvalidDataException $e) {
2614
            $violations = $e->getViolations();
2615
        }
2616
2617
        $this->assertNotNull($violations);
2618
        $this->assertCount(1, $violations);
2619
        $this->assertTrue($violations->has(0));
2620
        $this->assertSame(ErrorCode::MUST_BE_PROVIDED, $violations->get(0)->getCode());
2621
        $this->assertSame('This value must be provided.', $violations->get(0)->getMessage());
2622
        $this->assertSame('value', $violations->get(0)->getPropertyPath());
2623
    }
2624
2625
    /**
2626
     * @dataProvider strictBooleanProvider
2627
     * @dataProvider nonStrictBooleanProvider
2628
     */
2629
    public function testSourcelessType($element, bool $expected): void
2630
    {
2631
        $type = Type::fromName(BuiltinType::BOOL);
2632
2633
        $this->assertSame($expected, $this->createHydrator()->castValue($element, $type));
2634
    }
2635
2636
    public function strictNullProvider(): Generator
2637
    {
2638
        yield [null, null];
2639
    }
2640
2641
    public function nonStrictNullProvider(): Generator
2642
    {
2643
        yield ['', null];
2644
        yield [' ', null];
2645
    }
2646
2647
    public function strictBooleanProvider(): Generator
2648
    {
2649
        yield [true, true];
2650
        yield [false, false];
2651
    }
2652
2653
    public function nonStrictBooleanProvider(): Generator
2654
    {
2655
        yield ['1', true];
2656
        yield ['0', false];
2657
        yield ['true', true];
2658
        yield ['false', false];
2659
        yield ['yes', true];
2660
        yield ['no', false];
2661
        yield ['on', true];
2662
        yield ['off', false];
2663
    }
2664
2665
    public function notBooleanProvider(): Generator
2666
    {
2667
        yield [0];
2668
        yield [1];
2669
        yield [42];
2670
        yield [3.14159];
2671
        yield ['foo'];
2672
        yield [[]];
2673
    }
2674
2675
    public function strictIntegerProvider(): Generator
2676
    {
2677
        yield [-1, -1];
2678
        yield [0, 0];
2679
        yield [1, 1];
2680
    }
2681
2682
    public function nonStrictIntegerProvider(): Generator
2683
    {
2684
        yield ['-1', -1];
2685
        yield ['0', 0];
2686
        yield ['1', 1];
2687
        yield ['+1', 1];
2688
    }
2689
2690
    public function notIntegerProvider(): Generator
2691
    {
2692
        yield [true];
2693
        yield [false];
2694
        yield [3.14159];
2695
        yield ['foo'];
2696
        yield [[]];
2697
    }
2698
2699
    public function strictNumberProvider(): Generator
2700
    {
2701
        yield [-1, -1.];
2702
        yield [0, 0.];
2703
        yield [1, 1.];
2704
        yield [-1., -1.];
2705
        yield [0., 0.];
2706
        yield [1., 1.];
2707
        yield [-.1, -.1];
2708
        yield [.0, .0];
2709
        yield [.1, .1];
2710
    }
2711
2712
    public function nonStrictNumberProvider(): Generator
2713
    {
2714
        yield ['-1', -1.];
2715
        yield ['0', 0.];
2716
        yield ['1', 1.];
2717
        yield ['+1', 1.];
2718
        yield ['-1.', -1.];
2719
        yield ['0.', 0.];
2720
        yield ['1.', 1.];
2721
        yield ['+1.', 1.];
2722
        yield ['-.1', -.1];
2723
        yield ['.0', .0];
2724
        yield ['.1', .1];
2725
        yield ['+.1', .1];
2726
        yield ['-1.0', -1.];
2727
        yield ['0.0', 0.];
2728
        yield ['1.0', 1.];
2729
        yield ['+1.0', 1.];
2730
        yield ['1e-1', .1];
2731
        yield ['1e1', 10.];
2732
        yield ['1e+1', 10.];
2733
        yield ['1.e-1', .1];
2734
        yield ['1.e1', 10.];
2735
        yield ['1.e+1', 10.];
2736
        yield ['.1e-1', .01];
2737
        yield ['.1e1', 1.];
2738
        yield ['.1e+1', 1.];
2739
        yield ['1.0e-1', .1];
2740
        yield ['1.0e1', 10.];
2741
        yield ['1.0e+1', 10.];
2742
    }
2743
2744
    public function notNumberProvider(): Generator
2745
    {
2746
        yield [true];
2747
        yield [false];
2748
        yield ['foo'];
2749
        yield [[]];
2750
    }
2751
2752
    public function strictStringProvider(): Generator
2753
    {
2754
        yield ['foo', 'foo'];
2755
2756
        // Must not be cast to a null
2757
        yield ['', ''];
2758
        yield [' ', ' '];
2759
2760
        // Must not be cast to a boolean
2761
        yield ['true', 'true'];
2762
        yield ['false', 'false'];
2763
        yield ['yes', 'yes'];
2764
        yield ['no', 'no'];
2765
        yield ['on', 'on'];
2766
        yield ['off', 'off'];
2767
2768
        // Must not be cast to a number
2769
        yield ['-1', '-1'];
2770
        yield ['0', '0'];
2771
        yield ['1', '1'];
2772
        yield ['+1', '+1'];
2773
        yield ['-1.', '-1.'];
2774
        yield ['0.', '0.'];
2775
        yield ['1.', '1.'];
2776
        yield ['+1.', '+1.'];
2777
        yield ['-.1', '-.1'];
2778
        yield ['.0', '.0'];
2779
        yield ['.1', '.1'];
2780
        yield ['+.1', '+.1'];
2781
        yield ['-1.0', '-1.0'];
2782
        yield ['0.0', '0.0'];
2783
        yield ['1.0', '1.0'];
2784
        yield ['+1.0', '+1.0'];
2785
        yield ['1e-1', '1e-1'];
2786
        yield ['1e1', '1e1'];
2787
        yield ['1e+1', '1e+1'];
2788
        yield ['1.e-1', '1.e-1'];
2789
        yield ['1.e1', '1.e1'];
2790
        yield ['1.e+1', '1.e+1'];
2791
        yield ['.1e-1', '.1e-1'];
2792
        yield ['.1e1', '.1e1'];
2793
        yield ['.1e+1', '.1e+1'];
2794
        yield ['1.0e-1', '1.0e-1'];
2795
        yield ['1.0e1', '1.0e1'];
2796
        yield ['1.0e+1', '1.0e+1'];
2797
    }
2798
2799
    public function nonStrictStringProvider(): Generator
2800
    {
2801
        yield [-1, '-1'];
2802
        yield [0, '0'];
2803
        yield [1, '1'];
2804
    }
2805
2806
    public function strictNotStringProvider(): Generator
2807
    {
2808
        yield [true];
2809
        yield [false];
2810
        yield [42];
2811
        yield [3.14159];
2812
        yield [[]];
2813
    }
2814
2815
    public function nonStrictNotStringProvider(): Generator
2816
    {
2817
        yield [true];
2818
        yield [false];
2819
        yield [3.14159];
2820
        yield [[]];
2821
    }
2822
2823
    public function emptyArrayProvider(): Generator
2824
    {
2825
        yield [[]];
2826
    }
2827
2828
    public function notArrayProvider(): Generator
2829
    {
2830
        yield [true];
2831
        yield [false];
2832
        yield [42];
2833
        yield [3.14159];
2834
        yield ['foo'];
2835
    }
2836
2837
    public function strictNullDataProvider(): Generator
2838
    {
2839
        foreach ($this->strictNullProvider() as [$actual, $expected]) {
2840
            yield [['value' => $actual], $expected];
2841
        }
2842
    }
2843
2844
    public function nonStrictNullDataProvider(): Generator
2845
    {
2846
        foreach ($this->nonStrictNullProvider() as [$actual, $expected]) {
2847
            yield [['value' => $actual], $expected];
2848
        }
2849
    }
2850
2851
    public function strictBooleanDataProvider(): Generator
2852
    {
2853
        foreach ($this->strictBooleanProvider() as [$actual, $expected]) {
2854
            yield [['value' => $actual], $expected];
2855
        }
2856
    }
2857
2858
    public function nonStrictBooleanDataProvider(): Generator
2859
    {
2860
        foreach ($this->nonStrictBooleanProvider() as [$actual, $expected]) {
2861
            yield [['value' => $actual], $expected];
2862
        }
2863
    }
2864
2865
    public function notBooleanDataProvider(): Generator
2866
    {
2867
        foreach ($this->notBooleanProvider() as [$actual]) {
2868
            yield [['value' => $actual]];
2869
        }
2870
    }
2871
2872
    public function strictIntegerDataProvider(): Generator
2873
    {
2874
        foreach ($this->strictIntegerProvider() as [$actual, $expected]) {
2875
            yield [['value' => $actual], $expected];
2876
        }
2877
    }
2878
2879
    public function nonStrictIntegerDataProvider(): Generator
2880
    {
2881
        foreach ($this->nonStrictIntegerProvider() as [$actual, $expected]) {
2882
            yield [['value' => $actual], $expected];
2883
        }
2884
    }
2885
2886
    public function notIntegerDataProvider(): Generator
2887
    {
2888
        foreach ($this->notIntegerProvider() as [$actual]) {
2889
            yield [['value' => $actual]];
2890
        }
2891
    }
2892
2893
    public function strictNumberDataProvider(): Generator
2894
    {
2895
        foreach ($this->strictNumberProvider() as [$actual, $expected]) {
2896
            yield [['value' => $actual], $expected];
2897
        }
2898
    }
2899
2900
    public function nonStrictNumberDataProvider(): Generator
2901
    {
2902
        foreach ($this->nonStrictNumberProvider() as [$actual, $expected]) {
2903
            yield [['value' => $actual], $expected];
2904
        }
2905
    }
2906
2907
    public function notNumberDataProvider(): Generator
2908
    {
2909
        foreach ($this->notNumberProvider() as [$actual]) {
2910
            yield [['value' => $actual]];
2911
        }
2912
    }
2913
2914
    public function strictStringDataProvider(): Generator
2915
    {
2916
        foreach ($this->strictStringProvider() as [$actual, $expected]) {
2917
            yield [['value' => $actual], $expected];
2918
        }
2919
    }
2920
2921
    public function nonStrictStringDataProvider(): Generator
2922
    {
2923
        foreach ($this->nonStrictStringProvider() as [$actual, $expected]) {
2924
            yield [['value' => $actual], $expected];
2925
        }
2926
    }
2927
2928
    public function strictNotStringDataProvider(): Generator
2929
    {
2930
        foreach ($this->strictNotStringProvider() as [$actual]) {
2931
            yield [['value' => $actual]];
2932
        }
2933
    }
2934
2935
    public function nonStrictNotStringDataProvider(): Generator
2936
    {
2937
        foreach ($this->nonStrictNotStringProvider() as [$actual]) {
2938
            yield [['value' => $actual]];
2939
        }
2940
    }
2941
2942
    public function integerEnumDataProvider(): Generator
2943
    {
2944
        if (PHP_VERSION_ID < 80100) {
2945
            return [[[], null]];
2946
        }
2947
2948
        $foo = Fixture\IntegerEnum::FOO;
2949
        $bar = Fixture\IntegerEnum::BAR;
2950
        $baz = Fixture\IntegerEnum::BAZ;
2951
2952
        yield [['value' => $foo->value], $foo];
2953
        yield [['value' => $bar->value], $bar];
2954
        yield [['value' => $baz->value], $baz];
2955
2956
        yield [['value' => (string) $foo->value], $foo];
2957
        yield [['value' => (string) $bar->value], $bar];
2958
        yield [['value' => (string) $baz->value], $baz];
2959
    }
2960
2961
    public function stringEnumDataProvider(): Generator
2962
    {
2963
        if (PHP_VERSION_ID < 80100) {
2964
            return [[[], null]];
2965
        }
2966
2967
        $foo = Fixture\StringEnum::FOO;
2968
        $bar = Fixture\StringEnum::BAR;
2969
        $baz = Fixture\StringEnum::BAZ;
2970
2971
        yield [['value' => $foo->value], $foo];
2972
        yield [['value' => $bar->value], $bar];
2973
        yield [['value' => $baz->value], $baz];
2974
    }
2975
2976
    public function myclabsEnumDataProvider(): Generator
2977
    {
2978
        $foo = Fixture\MyclabsEnum::FOO();
0 ignored issues
show
Bug introduced by
The method FOO() does not exist on Sunrise\Hydrator\Tests\Fixture\MyclabsEnum. Since you implemented __callStatic, consider adding a @method annotation. ( Ignorable by Annotation )

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

2978
        /** @scrutinizer ignore-call */ 
2979
        $foo = Fixture\MyclabsEnum::FOO();
Loading history...
2979
        $bar = Fixture\MyclabsEnum::BAR();
0 ignored issues
show
Bug introduced by
The method BAR() does not exist on Sunrise\Hydrator\Tests\Fixture\MyclabsEnum. Since you implemented __callStatic, consider adding a @method annotation. ( Ignorable by Annotation )

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

2979
        /** @scrutinizer ignore-call */ 
2980
        $bar = Fixture\MyclabsEnum::BAR();
Loading history...
2980
        $baz = Fixture\MyclabsEnum::BAZ();
0 ignored issues
show
Bug introduced by
The method BAZ() does not exist on Sunrise\Hydrator\Tests\Fixture\MyclabsEnum. Since you implemented __callStatic, consider adding a @method annotation. ( Ignorable by Annotation )

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

2980
        /** @scrutinizer ignore-call */ 
2981
        $baz = Fixture\MyclabsEnum::BAZ();
Loading history...
2981
2982
        yield [['value' => $foo->getValue()], $foo];
2983
        yield [['value' => $bar->getValue()], $bar];
2984
        yield [['value' => $baz->getValue()], $baz];
2985
    }
2986
2987
    public function arrayDataProvider(): Generator
2988
    {
2989
        yield [['value' => []], []];
2990
        yield [['value' => ['foo']], ['foo']];
2991
    }
2992
2993
    public function notArrayDataProvider(): Generator
2994
    {
2995
        foreach ($this->notArrayProvider() as [$actual]) {
2996
            yield [['value' => $actual]];
2997
        }
2998
    }
2999
3000
    public function timestampDataProvider(): Generator
3001
    {
3002
        // default formatted timestamp
3003
        $timestamp = date(TimestampTypeConverter::DEFAULT_FORMAT);
3004
3005
        yield [['value' => $timestamp], $timestamp];
3006
        yield [['value' => $timestamp], $timestamp, TimestampTypeConverter::DEFAULT_FORMAT];
3007
3008
        yield [['value' => '700101'], '700101', 'ymd'];
3009
        yield [['value' => '000000'], '000000', 'His'];
3010
3011
        yield [['value' => '-1'], '-1', 'U'];
3012
        yield [['value' => '0'], '0', 'U'];
3013
        yield [['value' => '1'], '1', 'U'];
3014
        yield [['value' => '+1'], '1', 'U'];
3015
3016
        // Must be converted to a string...
3017
        yield [['value' => -1], '-1', 'U'];
3018
        yield [['value' => 0], '0', 'U'];
3019
        yield [['value' => 1], '1', 'U'];
3020
3021
        // The timezone must be applied...
3022
        yield [['value' => '00:00:00'], '00:00:00', 'H:i:s', 'Europe/Kiev'];
3023
3024
        // ISO 8601
3025
        yield [['value' => '00:00:00.123456'], '00:00:00.123456', 'H:i:s.u'];
3026
        yield [['value' => '00:00:00.123456+00:00'], '00:00:00.123456+00:00', 'H:i:s.uP'];
3027
        yield [['value' => 'Monday 00:00:00.123456'], 'Monday 00:00:00.123456', 'l H:i:s.u'];
3028
        yield [['value' => '00:00:00.1234567890'], '00:00:00.123456', 'H:i:s.u'];
3029
        yield [['value' => '00:00:00.1234567890+00:00'], '00:00:00.123456+00:00', 'H:i:s.uP'];
3030
        yield [['value' => 'Monday 00:00:00.1234567890'], 'Monday 00:00:00.123456', 'l H:i:s.u'];
3031
    }
3032
3033
    public function invalidTimestampDataProvider(): Generator
3034
    {
3035
        yield [
3036
            ['value' => '01/01/1970 00:00:00'],
3037
            'Y-m-d H:i:s',
3038
            ErrorCode::INVALID_TIMESTAMP,
3039
            'This value is not a valid timestamp; expected format: Y-m-d H:i:s.',
3040
        ];
3041
3042
        yield [
3043
            ['value' => '1970-01-01 00:00:00'],
3044
            'U',
3045
            ErrorCode::MUST_BE_INTEGER,
3046
            'This value must be of type integer.',
3047
        ];
3048
3049
        yield [
3050
            ['value' => 0],
3051
            'Y',
3052
            ErrorCode::MUST_BE_STRING,
3053
            'This value must be of type string.',
3054
        ];
3055
    }
3056
3057
    public function timezoneDataProvider(): Generator
3058
    {
3059
        foreach (DateTimeZone::listIdentifiers() as $timezone) {
3060
            yield [['value' => $timezone], $timezone];
3061
        }
3062
    }
3063
3064
    public function uuidDataProvider(): Generator
3065
    {
3066
        yield [['value' => '207ddb61-c300-4368-9f26-33d0a99eac00'], '207ddb61-c300-4368-9f26-33d0a99eac00'];
3067
    }
3068
3069
    private function createHydrator(array $context = []): HydratorInterface
3070
    {
3071
        $hydrator = new Hydrator($context);
3072
        if (PHP_VERSION_ID < 80000) {
3073
            $hydrator->useDefaultAnnotationReader();
0 ignored issues
show
Deprecated Code introduced by
The function Sunrise\Hydrator\Hydrato...faultAnnotationReader() has been deprecated: 3.2.0 Use the {@see setAnnotationReader()} method with the {@see DoctrineAnnotationReader::default()} attribute. ( Ignorable by Annotation )

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

3073
            /** @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...
3074
        }
3075
3076
        return $hydrator;
3077
    }
3078
3079
    private function phpRequired(string $version): void
3080
    {
3081
        if (version_compare(PHP_VERSION, $version, '<')) {
3082
            $this->markTestSkipped(sprintf('PHP %s is required.', $version));
3083
        }
3084
    }
3085
3086
    private function assertInvalidValueExceptionCount(int $expectedCount): void
3087
    {
3088
        $this->invalidValueExceptionCount = $expectedCount;
3089
    }
3090
3091
    private function assertInvalidValueExceptionMessage(int $exceptionIndex, string $expectedMessage): void
3092
    {
3093
        $this->invalidValueExceptionMessage[] = [$exceptionIndex, $expectedMessage];
3094
    }
3095
3096
    private function assertInvalidValueExceptionPropertyPath(int $exceptionIndex, string $expectedPropertyPath): void
3097
    {
3098
        $this->invalidValueExceptionPropertyPath[] = [$exceptionIndex, $expectedPropertyPath];
3099
    }
3100
3101
    private function assertInvalidValueExceptionErrorCode(int $exceptionIndex, string $expectedErrorCode): void
3102
    {
3103
        $this->invalidValueExceptionErrorCode[] = [$exceptionIndex, $expectedErrorCode];
3104
    }
3105
3106
    protected function runTest(): void
3107
    {
3108
        $invalidDataExceptionHandled = false;
3109
3110
        try {
3111
            parent::runTest();
3112
        } catch (InvalidDataException $invalidDataException) {
3113
            $invalidDataExceptionMessages = [];
3114
            foreach ($invalidDataException->getExceptions() as $invalidValueException) {
3115
                $invalidDataExceptionMessages[] = sprintf(
3116
                    '[%s] %s',
3117
                    $invalidValueException->getPropertyPath(),
3118
                    $invalidValueException->getMessage(),
3119
                );
3120
            }
3121
3122
            if (isset($this->invalidValueExceptionCount)) {
3123
                $invalidDataExceptionHandled = true;
3124
                $this->assertCount(
3125
                    $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

3125
                    /** @scrutinizer ignore-type */ $this->invalidValueExceptionCount,
Loading history...
3126
                    $invalidDataException->getExceptions(),
3127
                    \join(\PHP_EOL, $invalidDataExceptionMessages),
3128
                );
3129
            }
3130
3131
            foreach ($this->invalidValueExceptionMessage as [$index, $invalidValueExceptionMessage]) {
3132
                $invalidDataExceptionHandled = true;
3133
                $this->assertArrayHasKey($index, $invalidDataException->getExceptions());
3134
                $this->assertSame(
3135
                    $invalidValueExceptionMessage,
3136
                    $invalidDataException->getExceptions()[$index]->getMessage(),
3137
                );
3138
            }
3139
3140
            foreach ($this->invalidValueExceptionPropertyPath as [$index, $invalidValueExceptionPropertyPath]) {
3141
                $invalidDataExceptionHandled = true;
3142
                $this->assertArrayHasKey($index, $invalidDataException->getExceptions());
3143
                $this->assertSame(
3144
                    $invalidValueExceptionPropertyPath,
3145
                    $invalidDataException->getExceptions()[$index]->getPropertyPath(),
3146
                );
3147
            }
3148
3149
            foreach ($this->invalidValueExceptionErrorCode as [$index, $invalidValueExceptionErrorCode]) {
3150
                $invalidDataExceptionHandled = true;
3151
                $this->assertArrayHasKey($index, $invalidDataException->getExceptions());
3152
                $this->assertSame(
3153
                    $invalidValueExceptionErrorCode,
3154
                    $invalidDataException->getExceptions()[$index]->getErrorCode(),
3155
                );
3156
            }
3157
3158
            if (!$invalidDataExceptionHandled) {
3159
                throw $invalidDataException;
3160
            }
3161
        } finally {
3162
            $this->invalidValueExceptionCount = null;
3163
            $this->invalidValueExceptionMessage = [];
3164
            $this->invalidValueExceptionPropertyPath = [];
3165
            $this->invalidValueExceptionErrorCode = [];
3166
3167
            if ($invalidDataExceptionHandled) {
3168
                $this->assertTrue(true);
3169
            }
3170
        }
3171
    }
3172
}
3173