Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions phpstan-baseline.neon
Original file line number Diff line number Diff line change
Expand Up @@ -31,21 +31,21 @@ parameters:
path: src/Metadata/AttributeMetadataFactory.php

-
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$#'
message: '#^Method Patchlevel\\Hydrator\\Metadata\\AttributeMetadataFactory\:\:guessNormalizerByObjectType\(\) has parameter \$type with generic class Symfony\\Component\\TypeInfo\\Type\\ObjectType but does not specify its types\: T$#'
identifier: missingType.generics
count: 1
path: src/Metadata/AttributeMetadataFactory.php

-
message: '#^Parameter \#1 \$enum of class Patchlevel\\Hydrator\\Normalizer\\EnumNormalizer constructor expects class\-string\<BackedEnum\>\|null, string given\.$#'
message: '#^Parameter \#1 \$class of method Patchlevel\\Hydrator\\Metadata\\AttributeMetadataFactory\:\:findNormalizerOnClass\(\) expects class\-string, string given\.$#'
identifier: argument.type
count: 1
count: 2
path: src/Metadata/AttributeMetadataFactory.php

-
message: '#^Parameter \#1 \$objectOrClass of class ReflectionClass constructor expects class\-string\<T of object\>\|T of object, string given\.$#'
message: '#^Parameter \#1 \$enum of class Patchlevel\\Hydrator\\Normalizer\\EnumNormalizer constructor expects class\-string\<BackedEnum\>\|null, string given\.$#'
identifier: argument.type
count: 2
count: 1
path: src/Metadata/AttributeMetadataFactory.php

-
Expand Down
106 changes: 58 additions & 48 deletions src/Metadata/AttributeMetadataFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -225,38 +225,6 @@
return $attributeReflectionList[0]->newInstance()->name();
}

private function getNormalizer(ReflectionProperty $reflectionProperty): Normalizer|null
{
$type = $this->typeResolver->resolve($reflectionProperty);

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

if ($normalizer instanceof TypeAwareNormalizer) {
$normalizer->handleType($type);
}

if ($normalizer instanceof ReflectionTypeAwareNormalizer) {
$reflectionPropertyType = $reflectionProperty->getType();
$normalizer->handleReflectionType($reflectionPropertyType);
}

return $normalizer;
}

private function inferNormalizer(ObjectType $type): Normalizer|null
{
if ($type instanceof BackedEnumType) {
return new EnumNormalizer($type->getClassName());
}

return match ($type->getClassName()) {
DateTimeImmutable::class => new DateTimeImmutableNormalizer(),
DateTime::class => new DateTimeNormalizer(),
DateTimeZone::class => new DateTimeZoneNormalizer(),
default => null,
};
}

private function hasIgnore(ReflectionProperty $reflectionProperty): bool
{
return $reflectionProperty->getAttributes(Ignore::class) !== [];
Expand Down Expand Up @@ -334,13 +302,13 @@
return $this->getFieldName($property);
}

/** @return array{bool, mixed, (callable(string, mixed):mixed)|null} */

Check failure on line 305 in src/Metadata/AttributeMetadataFactory.php

View workflow job for this annotation

GitHub Actions / Static Analysis by Psalm (locked, 8.4, ubuntu-latest)

InvalidReturnType

src/Metadata/AttributeMetadataFactory.php:305:17: InvalidReturnType: The declared return type 'list{bool, mixed, callable(string, mixed):mixed|null}' for Patchlevel\Hydrator\Metadata\AttributeMetadataFactory::getPersonalData is incorrect, got 'list{0: bool, 1: mixed|null, 2?: callable(string, mixed):mixed|null}' which is different due to additional array shape fields (2) (see https://psalm.dev/011)
private function getPersonalData(ReflectionProperty $reflectionProperty): array
{
$attributeReflectionList = $reflectionProperty->getAttributes(PersonalData::class);

if ($attributeReflectionList === []) {
return [false, null];

Check failure on line 311 in src/Metadata/AttributeMetadataFactory.php

View workflow job for this annotation

GitHub Actions / Static Analysis by Psalm (locked, 8.4, ubuntu-latest)

InvalidReturnStatement

src/Metadata/AttributeMetadataFactory.php:311:20: InvalidReturnStatement: The inferred type 'list{false, null}' does not match the declared return type 'list{bool, mixed, callable(string, mixed):mixed|null}' for Patchlevel\Hydrator\Metadata\AttributeMetadataFactory::getPersonalData (see https://psalm.dev/128)
}

$attribute = $attributeReflectionList[0]->newInstance();
Expand All @@ -367,7 +335,28 @@
}
}

private function findNormalizer(ReflectionProperty $reflectionProperty, Type $type): Normalizer|null
private function getNormalizer(ReflectionProperty $reflectionProperty): Normalizer|null
{
$normalizer = $this->findNormalizerOnProperty($reflectionProperty);
$type = null;

if (!$normalizer) {
$type = $this->typeResolver->resolve($reflectionProperty);
$normalizer = $this->inferNormalizerByType($type);
}

if ($normalizer instanceof TypeAwareNormalizer) {
$normalizer->handleType($type ?? $this->typeResolver->resolve($reflectionProperty));
}

if ($normalizer instanceof ReflectionTypeAwareNormalizer) {
$normalizer->handleReflectionType($reflectionProperty->getType());
}

return $normalizer;
}

private function findNormalizerOnProperty(ReflectionProperty $reflectionProperty): Normalizer|null
{
$attributeReflectionList = $reflectionProperty->getAttributes(
Normalizer::class,
Expand All @@ -378,18 +367,27 @@
return $attributeReflectionList[0]->newInstance();
}

return null;
}

private function inferNormalizerByType(Type $type): Normalizer|null
{
if ($type instanceof NullableType) {
$type = $type->getWrappedType();
}

if ($type instanceof BackedEnumType) {
return new EnumNormalizer($type->getClassName());
}

if ($type instanceof ObjectType) {
$normalizer = $this->findNormalizerOnClass(new ReflectionClass($type->getClassName()));
$normalizer = $this->findNormalizerOnClass($type->getClassName());

if ($normalizer) {
return $normalizer;
}

return $this->inferNormalizer($type);
return $this->guessNormalizerByObjectType($type);
}

if ($type instanceof CollectionType) {
Expand All @@ -399,29 +397,31 @@
$valueType = $type->getWrappedType();
}

if (!$valueType instanceof ObjectType) {
return null;
}
$normalizer = null;

$normalizer = $this->findNormalizerOnClass(new ReflectionClass($valueType->getClassName()));
if ($valueType instanceof ObjectType) {
$normalizer = $this->findNormalizerOnClass($valueType->getClassName());
}

if ($normalizer === null) {
$normalizer = $this->inferNormalizer($valueType);
$normalizer = $this->inferNormalizerByType($valueType);
}

if ($normalizer === null) {
return null;
}
if ($normalizer) {
return new ArrayNormalizer($normalizer);
}

return new ArrayNormalizer($normalizer);
return null;
}

return null;
}

/** @param ReflectionClass<object> $reflectionClass */
private function findNormalizerOnClass(ReflectionClass $reflectionClass): Normalizer|null
/** @param class-string $class */
private function findNormalizerOnClass(string $class): Normalizer|null
{
$reflectionClass = new ReflectionClass($class);

$attributes = $reflectionClass->getAttributes(
Normalizer::class,
ReflectionAttribute::IS_INSTANCEOF,
Expand All @@ -434,15 +434,15 @@
$parent = $reflectionClass->getParentClass();

if ($parent) {
$normalizer = $this->findNormalizerOnClass($parent);
$normalizer = $this->findNormalizerOnClass($parent->getName());

if ($normalizer !== null) {
return $normalizer;
}
}

foreach ($reflectionClass->getInterfaces() as $interface) {
$normalizer = $this->findNormalizerOnClass($interface);
$normalizer = $this->findNormalizerOnClass($interface->getName());

if ($normalizer !== null) {
return $normalizer;
Expand All @@ -451,4 +451,14 @@

return null;
}

private function guessNormalizerByObjectType(ObjectType $type): Normalizer|null
{
return match ($type->getClassName()) {
DateTimeImmutable::class => new DateTimeImmutableNormalizer(),
DateTime::class => new DateTimeNormalizer(),
DateTimeZone::class => new DateTimeZoneNormalizer(),
default => null,
};
}
}
2 changes: 2 additions & 0 deletions tests/Unit/Fixture/InferNormalizerWithIterablesDto.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,15 @@ final class InferNormalizerWithIterablesDto
* @param list<Status> $listArray
* @param iterable<Status> $iterableArray
* @param array<string, Status> $hashMap
* @param array<string, iterable<Status>> $nested
* @param array{foo: string, bar: int, baz: list<string>}|null $jsonArray
*/
public function __construct(
public array $defaultArray = [],
public array $listArray = [],
public iterable $iterableArray = [],
public array $hashMap = [],
public iterable $nested = [],
public array|null $jsonArray = null,
) {
}
Expand Down
5 changes: 5 additions & 0 deletions tests/Unit/MetadataHydratorTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -466,6 +466,10 @@ public function testHydrateWithInferNormalizerWitIterables(): void
'foo' => Status::Draft,
'bar' => Status::Draft,
],
[
'foo' => [Status::Draft],
'bar' => [Status::Draft],
],
[
'foo' => 'php',
'bar' => 15,
Expand All @@ -480,6 +484,7 @@ public function testHydrateWithInferNormalizerWitIterables(): void
'listArray' => ['draft'],
'iterableArray' => ['draft'],
'hashMap' => ['foo' => 'draft', 'bar' => 'draft'],
'nested' => ['foo' => ['draft'], 'bar' => ['draft']],
'jsonArray' => ['foo' => 'php', 'bar' => 15, 'baz' => ['test']],
],
);
Expand Down
Loading