Skip to content

Commit 8d70a36

Browse files
authored
Merge pull request #83 from patchlevel/fix-infer-normalizer-with-iterables
fix infer normalizers with iterables
2 parents d583af6 + 5f1b39a commit 8d70a36

File tree

9 files changed

+115
-37
lines changed

9 files changed

+115
-37
lines changed

phpstan-baseline.neon

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,18 @@ parameters:
3030
count: 1
3131
path: src/Metadata/AttributeMetadataFactory.php
3232

33+
-
34+
message: '#^Method Patchlevel\\Hydrator\\Metadata\\AttributeMetadataFactory\:\:inferNormalizer\(\) has parameter \$type with generic class Symfony\\Component\\TypeInfo\\Type\\ObjectType but does not specify its types\: T$#'
35+
identifier: missingType.generics
36+
count: 1
37+
path: src/Metadata/AttributeMetadataFactory.php
38+
39+
-
40+
message: '#^Parameter \#1 \$enum of class Patchlevel\\Hydrator\\Normalizer\\EnumNormalizer constructor expects class\-string\<BackedEnum\>\|null, string given\.$#'
41+
identifier: argument.type
42+
count: 1
43+
path: src/Metadata/AttributeMetadataFactory.php
44+
3345
-
3446
message: '#^Parameter \#1 \$objectOrClass of class ReflectionClass constructor expects class\-string\<T of object\>\|T of object, string given\.$#'
3547
identifier: argument.type

src/Metadata/AttributeMetadataFactory.php

Lines changed: 16 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44

55
namespace Patchlevel\Hydrator\Metadata;
66

7-
use BackedEnum;
87
use DateTime;
98
use DateTimeImmutable;
109
use DateTimeZone;
@@ -25,9 +24,9 @@
2524
use ReflectionAttribute;
2625
use ReflectionClass;
2726
use ReflectionException;
28-
use ReflectionNamedType;
2927
use ReflectionProperty;
3028
use Symfony\Component\TypeInfo\Type;
29+
use Symfony\Component\TypeInfo\Type\BackedEnumType;
3130
use Symfony\Component\TypeInfo\Type\CollectionType;
3231
use Symfony\Component\TypeInfo\Type\NullableType;
3332
use Symfony\Component\TypeInfo\Type\ObjectType;
@@ -36,7 +35,6 @@
3635
use function array_key_exists;
3736
use function array_merge;
3837
use function array_values;
39-
use function is_a;
4038

4139
final class AttributeMetadataFactory implements MetadataFactory
4240
{
@@ -233,10 +231,6 @@ private function getNormalizer(ReflectionProperty $reflectionProperty): Normaliz
233231

234232
$normalizer = $this->findNormalizer($reflectionProperty, $type);
235233

236-
if (!$normalizer) {
237-
$normalizer = $this->inferNormalizer($reflectionProperty);
238-
}
239-
240234
if ($normalizer instanceof TypeAwareNormalizer) {
241235
$normalizer->handleType($type);
242236
}
@@ -249,32 +243,18 @@ private function getNormalizer(ReflectionProperty $reflectionProperty): Normaliz
249243
return $normalizer;
250244
}
251245

252-
private function inferNormalizer(ReflectionProperty $property): Normalizer|null
246+
private function inferNormalizer(ObjectType $type): Normalizer|null
253247
{
254-
$type = $property->getType();
255-
256-
if (!$type instanceof ReflectionNamedType) {
257-
return null;
248+
if ($type instanceof BackedEnumType) {
249+
return new EnumNormalizer($type->getClassName());
258250
}
259251

260-
$className = $type->getName();
261-
262-
$normalizer = match ($className) {
252+
return match ($type->getClassName()) {
263253
DateTimeImmutable::class => new DateTimeImmutableNormalizer(),
264254
DateTime::class => new DateTimeNormalizer(),
265255
DateTimeZone::class => new DateTimeZoneNormalizer(),
266256
default => null,
267257
};
268-
269-
if ($normalizer) {
270-
return $normalizer;
271-
}
272-
273-
if (is_a($className, BackedEnum::class, true)) {
274-
return new EnumNormalizer($className);
275-
}
276-
277-
return null;
278258
}
279259

280260
private function hasIgnore(ReflectionProperty $reflectionProperty): bool
@@ -403,7 +383,13 @@ private function findNormalizer(ReflectionProperty $reflectionProperty, Type $ty
403383
}
404384

405385
if ($type instanceof ObjectType) {
406-
return $this->findNormalizerOnClass(new ReflectionClass($type->getClassName()));
386+
$normalizer = $this->findNormalizerOnClass(new ReflectionClass($type->getClassName()));
387+
388+
if ($normalizer) {
389+
return $normalizer;
390+
}
391+
392+
return $this->inferNormalizer($type);
407393
}
408394

409395
if ($type instanceof CollectionType) {
@@ -420,16 +406,11 @@ private function findNormalizer(ReflectionProperty $reflectionProperty, Type $ty
420406
$normalizer = $this->findNormalizerOnClass(new ReflectionClass($valueType->getClassName()));
421407

422408
if ($normalizer === null) {
423-
return null;
424-
}
425-
426-
if ($normalizer instanceof TypeAwareNormalizer) {
427-
$normalizer->handleType($valueType);
428-
}
409+
$normalizer = $this->inferNormalizer($valueType);
429410

430-
if ($normalizer instanceof ReflectionTypeAwareNormalizer) {
431-
$reflectionPropertyType = $reflectionProperty->getType();
432-
$normalizer->handleReflectionType($reflectionPropertyType);
411+
if ($normalizer === null) {
412+
return null;
413+
}
433414
}
434415

435416
return new ArrayNormalizer($normalizer);

src/Normalizer/ArrayNormalizer.php

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
use Patchlevel\Hydrator\Hydrator;
99
use Symfony\Component\TypeInfo\Type;
1010
use Symfony\Component\TypeInfo\Type\CollectionType;
11+
use Symfony\Component\TypeInfo\Type\NullableType;
1112

1213
use function array_map;
1314
use function is_array;
@@ -59,6 +60,14 @@ public function setHydrator(Hydrator $hydrator): void
5960

6061
public function handleType(Type|null $type): void
6162
{
63+
if ($type === null) {
64+
return;
65+
}
66+
67+
if ($type instanceof NullableType) {
68+
$type = $type->getWrappedType();
69+
}
70+
6271
if (!$type instanceof CollectionType || !$this->normalizer instanceof TypeAwareNormalizer) {
6372
return;
6473
}

src/Normalizer/EnumNormalizer.php

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
use ReflectionType;
1010
use Symfony\Component\TypeInfo\Type;
1111
use Symfony\Component\TypeInfo\Type\BackedEnumType;
12+
use Symfony\Component\TypeInfo\Type\NullableType;
1213
use Throwable;
1314

1415
use function is_int;
@@ -68,7 +69,15 @@ public function handleReflectionType(ReflectionType|null $reflectionType): void
6869

6970
public function handleType(Type|null $type): void
7071
{
71-
if ($this->enum !== null || !$type instanceof BackedEnumType) {
72+
if ($this->enum !== null || $type === null) {
73+
return;
74+
}
75+
76+
if ($type instanceof NullableType) {
77+
$type = $type->getWrappedType();
78+
}
79+
80+
if (!$type instanceof BackedEnumType) {
7281
return;
7382
}
7483

src/Normalizer/ObjectNormalizer.php

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
use Patchlevel\Hydrator\Hydrator;
99
use ReflectionType;
1010
use Symfony\Component\TypeInfo\Type;
11+
use Symfony\Component\TypeInfo\Type\NullableType;
1112
use Symfony\Component\TypeInfo\Type\ObjectType;
1213

1314
use function is_array;
@@ -78,7 +79,15 @@ public function handleReflectionType(ReflectionType|null $reflectionType): void
7879

7980
public function handleType(Type|null $type): void
8081
{
81-
if ($this->className !== null || !$type instanceof ObjectType) {
82+
if ($type === null || $this->className !== null) {
83+
return;
84+
}
85+
86+
if ($type instanceof NullableType) {
87+
$type = $type->getWrappedType();
88+
}
89+
90+
if (!$type instanceof ObjectType) {
8291
return;
8392
}
8493

src/Normalizer/ReflectionTypeAwareNormalizer.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
use ReflectionType;
88

9+
/** @deprecated use TypeAwareNormalizer instead */
910
interface ReflectionTypeAwareNormalizer
1011
{
1112
/**

src/Normalizer/ReflectionTypeUtil.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
use function class_exists;
1111
use function is_a;
1212

13+
/** @deprecated use symfony/type-info api instead */
1314
final class ReflectionTypeUtil
1415
{
1516
public static function type(ReflectionType $reflectionType): string
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Patchlevel\Hydrator\Tests\Unit\Fixture;
6+
7+
final class InferNormalizerWithIterablesDto
8+
{
9+
/**
10+
* @param Status[] $defaultArray
11+
* @param list<Status> $listArray
12+
* @param iterable<Status> $iterableArray
13+
* @param array<string, Status> $hashMap
14+
* @param array{foo: string, bar: int, baz: list<string>}|null $jsonArray
15+
*/
16+
public function __construct(
17+
public array $defaultArray = [],
18+
public array $listArray = [],
19+
public iterable $iterableArray = [],
20+
public array $hashMap = [],
21+
public array|null $jsonArray = null,
22+
) {
23+
}
24+
}

tests/Unit/MetadataHydratorTest.php

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
use Patchlevel\Hydrator\Tests\Unit\Fixture\Email;
2626
use Patchlevel\Hydrator\Tests\Unit\Fixture\InferNormalizerBrokenDto;
2727
use Patchlevel\Hydrator\Tests\Unit\Fixture\InferNormalizerDto;
28+
use Patchlevel\Hydrator\Tests\Unit\Fixture\InferNormalizerWithIterablesDto;
2829
use Patchlevel\Hydrator\Tests\Unit\Fixture\InferNormalizerWithNullableDto;
2930
use Patchlevel\Hydrator\Tests\Unit\Fixture\NormalizerInBaseClassDefinedDto;
3031
use Patchlevel\Hydrator\Tests\Unit\Fixture\ParentDto;
@@ -455,6 +456,37 @@ public function testHydrateWithInferNormalizerAndNullableProperties(): void
455456
self::assertEquals($expected, $event);
456457
}
457458

459+
public function testHydrateWithInferNormalizerWitIterables(): void
460+
{
461+
$expected = new InferNormalizerWithIterablesDto(
462+
[Status::Draft],
463+
[Status::Draft],
464+
[Status::Draft],
465+
[
466+
'foo' => Status::Draft,
467+
'bar' => Status::Draft,
468+
],
469+
[
470+
'foo' => 'php',
471+
'bar' => 15,
472+
'baz' => ['test'],
473+
],
474+
);
475+
476+
$event = $this->hydrator->hydrate(
477+
InferNormalizerWithIterablesDto::class,
478+
[
479+
'defaultArray' => ['draft'],
480+
'listArray' => ['draft'],
481+
'iterableArray' => ['draft'],
482+
'hashMap' => ['foo' => 'draft', 'bar' => 'draft'],
483+
'jsonArray' => ['foo' => 'php', 'bar' => 15, 'baz' => ['test']],
484+
],
485+
);
486+
487+
self::assertEquals($expected, $event);
488+
}
489+
458490
public function testHydrateWithInferNormalizerFailed(): void
459491
{
460492
$this->expectException(TypeMismatch::class);

0 commit comments

Comments
 (0)