diff --git a/src/ClassExpander.php b/src/ClassExpander.php index 468533b..4cd620a 100644 --- a/src/ClassExpander.php +++ b/src/ClassExpander.php @@ -5,6 +5,7 @@ namespace EventSauce\ObjectHydrator; use function array_key_exists; +use function array_push; use function array_unique; use function array_values; use function class_exists; @@ -41,12 +42,12 @@ public static function expandClassesForHydration(array $classes, DefinitionProvi $className = (string) $propertyDefinition->firstTypeName; if ( ! in_array($className, $classes) && static::isClass($className)) { - $classes[] = $className; + array_push($classes, ...static::expandClassesForSerialization([$className], $definitionProvider)); } } } - return $classes; + return array_unique($classes); } private static function isClass(string $className): bool @@ -71,14 +72,14 @@ public static function expandClassesForSerialization( /** @var PropertySerializationDefinition $property */ foreach ($classDefinition->properties as $property) { - $type = $property->type; + $type = $property->propertyType->firstTypeName(); if ( ! in_array($type, $classes) && self::isClass($type)) { - $classes[] = $type; + array_push($classes, ...static::expandClassesForSerialization([$type], $definitionProvider)); } } } - return $classes; + return array_unique($classes); } } diff --git a/src/DefinitionProvider.php b/src/DefinitionProvider.php index 197068b..5247b19 100644 --- a/src/DefinitionProvider.php +++ b/src/DefinitionProvider.php @@ -4,40 +4,23 @@ namespace EventSauce\ObjectHydrator; -use ReflectionAttribute; -use ReflectionClass; -use ReflectionMethod; -use ReflectionNamedType; -use ReflectionParameter; -use ReflectionProperty; -use ReflectionUnionType; -use function array_key_exists; -use function array_reverse; -use function count; -use function is_a; - final class DefinitionProvider { /** @var array */ private array $definitionCache = []; - private DefaultCasterRepository $defaultCasters; - private KeyFormatter $keyFormatter; - private DefaultSerializerRepository $defaultSerializers; - private PropertyTypeResolver $propertyTypeResolver; private bool $serializePublicMethods; public function __construct( - DefaultCasterRepository $defaultCasterRepository = null, - KeyFormatter $keyFormatter = null, + DefaultCasterRepository $defaultCasterRepository = null, + KeyFormatter $keyFormatter = null, DefaultSerializerRepository $defaultSerializerRepository = null, - PropertyTypeResolver $propertyTypeResolver = null, - bool $serializePublicMethods = true, - ) - { + PropertyTypeResolver $propertyTypeResolver = null, + bool $serializePublicMethods = true, + ) { $this->defaultCasters = $defaultCasterRepository ?? DefaultCasterRepository::builtIn(); $this->keyFormatter = $keyFormatter ?? new KeyFormatterForSnakeCasing(); $this->defaultSerializers = $defaultSerializerRepository ?? DefaultSerializerRepository::builtIn(); @@ -57,11 +40,11 @@ public function provideDefinition(string $className): ClassHydrationDefinition public function provideHydrationDefinition(string $className): ClassHydrationDefinition { - if (array_key_exists($className, $this->definitionCache)) { + if (\array_key_exists($className, $this->definitionCache)) { return $this->definitionCache[$className]; } - $reflectionClass = new ReflectionClass($className); + $reflectionClass = new \ReflectionClass($className); $constructor = $this->resolveConstructor($reflectionClass); $classAttributes = $reflectionClass->getAttributes(); @@ -69,7 +52,7 @@ public function provideHydrationDefinition(string $className): ClassHydrationDef $definitions = []; $constructionStyle = match (true) { - $constructor instanceof ReflectionMethod => $constructor->isConstructor() ? 'new' : 'static', + $constructor instanceof \ReflectionMethod => $constructor->isConstructor() ? 'new' : 'static', ! $reflectionClass->isInstantiable() => 'none', default => 'new', }; @@ -80,8 +63,8 @@ public function provideHydrationDefinition(string $className): ClassHydrationDef $constructorName = ''; } - /** @var ReflectionParameter[] $parameters */ - $parameters = $constructor instanceof ReflectionMethod ? $constructor->getParameters() : []; + /** @var \ReflectionParameter[] $parameters */ + $parameters = $constructor instanceof \ReflectionMethod ? $constructor->getParameters() : []; foreach ($parameters as $parameter) { $accessorName = $parameter->getName(); @@ -99,14 +82,14 @@ public function provideHydrationDefinition(string $className): ClassHydrationDef $keys = $attribute->newInstance()->keys; } - if (is_a($attributeName, PropertyCaster::class, true)) { + if (\is_a($attributeName, PropertyCaster::class, true)) { $casters[] = [$attributeName, $attribute->getArguments()]; } } - if ($firstTypeName && count($casters) === 0 && $defaultCaster = $this->defaultCasters->casterFor( - $firstTypeName - )) { + if ($firstTypeName && \count($casters) === 0 && $defaultCaster = $this->defaultCasters->casterFor( + $firstTypeName + )) { $casters = [$defaultCaster]; } @@ -139,14 +122,14 @@ public function provideHydrationDefinition(string $className): ClassHydrationDef ); } - private function resolveConstructor(ReflectionClass $reflectionClass): ?ReflectionMethod + private function resolveConstructor(\ReflectionClass $reflectionClass): ?\ReflectionMethod { - $methods = $reflectionClass->getMethods(ReflectionMethod::IS_STATIC | ReflectionMethod::IS_PUBLIC); + $methods = $reflectionClass->getMethods(\ReflectionMethod::IS_STATIC | \ReflectionMethod::IS_PUBLIC); foreach ($methods as $method) { $isConstructor = $method->getAttributes(Constructor::class); - if (count($isConstructor) !== 0) { + if (\count($isConstructor) !== 0) { return $method; } } @@ -154,35 +137,40 @@ private function resolveConstructor(ReflectionClass $reflectionClass): ?Reflecti return $reflectionClass->getConstructor(); } - private function stringifyConstructor(ReflectionMethod $constructor): string + private function stringifyConstructor(\ReflectionMethod $constructor): string { return $constructor->getDeclaringClass()->getName() . '::' . $constructor->getName(); } public function provideSerializationDefinition(string $className): ClassSerializationDefinition { - $reflection = new ReflectionClass($className); + $reflection = new \ReflectionClass($className); $objectSettings = $this->resolveObjectSettings($reflection); $classAttributes = $reflection->getAttributes(); $properties = []; $publicMethods = []; if ($this->serializePublicMethods) { - $publicMethods = $reflection->getMethods(ReflectionMethod::IS_PUBLIC); + $publicMethods = $reflection->getMethods(\ReflectionMethod::IS_PUBLIC); } foreach ($publicMethods as $method) { if ($objectSettings->serializePublicMethods === false || $method->isStatic() + || $method->isConstructor() || $method->getNumberOfParameters() !== 0 - || count($method->getAttributes(DoNotSerialize::class)) === 1) { + || \count($method->getAttributes(DoNotSerialize::class)) === 1) { continue; } $methodName = $method->getShortName(); $key = $this->keyFormatter->propertyNameToKey($methodName); - /** @var ReflectionNamedType|ReflectionUnionType $returnType */ $returnType = $method->getReturnType(); + + if ($returnType === null) { + continue; + } + $attributes = $method->getAttributes(); $typeSpecifier = $this->typeSpecifier($attributes); $properties[] = new PropertySerializationDefinition( @@ -197,12 +185,12 @@ public function provideSerializationDefinition(string $className): ClassSerializ ); } - $publicProperties = $reflection->getProperties(ReflectionProperty::IS_PUBLIC); + $publicProperties = $reflection->getProperties(\ReflectionProperty::IS_PUBLIC); foreach ($publicProperties as $property) { if ($property->isStatic() || $objectSettings->serializePublicProperties === false - || count($property->getAttributes(DoNotSerialize::class)) === 1) { + || \count($property->getAttributes(DoNotSerialize::class)) === 1) { continue; } @@ -212,7 +200,7 @@ public function provideSerializationDefinition(string $className): ClassSerializ $serializers = $this->resolveSerializers($propertyType, $attributes); if ($property->isPromoted()) { - $serializers = array_reverse($serializers); + $serializers = \array_reverse($serializers); } $typeSpecifier = $this->typeSpecifier($attributes); @@ -239,7 +227,7 @@ public function provideSerializationDefinition(string $className): ClassSerializ } /** - * @param ReflectionAttribute[] $attributes + * @param \ReflectionAttribute[] $attributes */ private function typeSpecifier(array $attributes): ?MapToType { @@ -256,7 +244,7 @@ private function resolveSerializer(string $type, array $attributes): array { $serializers = $this->resolveSerializersFromAttributes($attributes); - if (count($serializers) === 0 && $default = $this->defaultSerializers->serializerForType($type)) { + if (\count($serializers) === 0 && $default = $this->defaultSerializers->serializerForType($type)) { $serializers[] = $default; } @@ -274,19 +262,19 @@ public function allSerializers(): array } /** - * @param ReflectionAttribute[] $attributes + * @param \ReflectionAttribute[] $attributes * * @return array, 1: array}>|array, 1: array}> */ - private function resolveSerializers(ReflectionUnionType|ReflectionNamedType $type, array $attributes): array + private function resolveSerializers(\ReflectionUnionType|\ReflectionNamedType $type, array $attributes): array { $attributeSerializer = $this->resolveSerializersFromAttributes($attributes); - if (count($attributeSerializer) !== 0) { + if (\count($attributeSerializer) !== 0) { return $attributeSerializer; } - if ($type instanceof ReflectionNamedType) { + if ($type instanceof \ReflectionNamedType) { return [$this->defaultSerializers->serializerForType($type->getName())]; } @@ -301,7 +289,7 @@ private function resolveSerializers(ReflectionUnionType|ReflectionNamedType $typ } /** - * @param ReflectionAttribute[] $attributes + * @param \ReflectionAttribute[] $attributes */ private function resolveSerializersFromAttributes(array $attributes): array { @@ -310,7 +298,7 @@ private function resolveSerializersFromAttributes(array $attributes): array foreach ($attributes as $attribute) { $name = $attribute->getName(); - if (is_a($name, PropertySerializer::class, true)) { + if (\is_a($name, PropertySerializer::class, true)) { $serializers[] = [$attribute->getName(), $attribute->getArguments()]; } } @@ -324,7 +312,7 @@ public function hasSerializerFor(string $name): bool } /** - * @param ReflectionAttribute[] $attributes + * @param \ReflectionAttribute[] $attributes * * @return array> */ @@ -334,7 +322,7 @@ private function resolveKeys(string $defaultKey, array $attributes): array } /** - * @param ReflectionAttribute[] $attributes + * @param \ReflectionAttribute[] $attributes */ private function resolveMapFrom(array $attributes): array|false { @@ -350,7 +338,7 @@ private function resolveMapFrom(array $attributes): array|false return false; } - private function resolveObjectSettings(ReflectionClass $reflection): MapperSettings + private function resolveObjectSettings(\ReflectionClass $reflection): MapperSettings { $attributes = $this->getMapperAttributes($reflection); @@ -362,9 +350,9 @@ private function resolveObjectSettings(ReflectionClass $reflection): MapperSetti } /** - * @return ReflectionAttribute[] + * @return \ReflectionAttribute[] */ - private function getMapperAttributes(ReflectionClass $reflection): array + private function getMapperAttributes(\ReflectionClass $reflection): array { $attributes = $reflection->getAttributes(MapperSettings::class); diff --git a/src/Issue75/.gitignore b/src/Issue75/.gitignore new file mode 100644 index 0000000..60edec8 --- /dev/null +++ b/src/Issue75/.gitignore @@ -0,0 +1 @@ +*Hydrator.php \ No newline at end of file diff --git a/src/Issue75/Amount.php b/src/Issue75/Amount.php new file mode 100644 index 0000000..33aac56 --- /dev/null +++ b/src/Issue75/Amount.php @@ -0,0 +1,12 @@ +exclude('*Test.php') + ->findAllNames(); + $dumper = new ObjectMapperCodeGenerator(); + $code = $dumper->dump($classes, Issue75Hydrator::class); + \file_put_contents(__DIR__ . '/Issue75Hydrator.php', $code); + + $hydrator = new Issue75Hydrator(); + $instance = $hydrator->hydrateObject(TopLevel::class, $values = [ + 'number' => 30, + 'lower' => [ + 'amount' => [ + 'amount' => 100, + ], + 'slot' => [ + 'name' => 'name', + 'value' => 'value', + ], + 'items' => [ + ['value' => 'one'], + ['value' => 'two'], + ], + ], + ]); + + $serialized = $hydrator->serializeObject($instance); + + self::assertEquals($values, $serialized); + } +} diff --git a/src/Issue75/ListItem.php b/src/Issue75/ListItem.php new file mode 100644 index 0000000..7437552 --- /dev/null +++ b/src/Issue75/ListItem.php @@ -0,0 +1,12 @@ +