Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Try to reproduce issue #75 #76

Open
wants to merge 6 commits into
base: main
Choose a base branch
from
Open
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
11 changes: 6 additions & 5 deletions src/ClassExpander.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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
Expand All @@ -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);
}
}
98 changes: 43 additions & 55 deletions src/DefinitionProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -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<class-string, ClassHydrationDefinition> */
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();
Expand All @@ -57,19 +40,19 @@ 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();

/** @var PropertyHydrationDefinition[] $definitions */
$definitions = [];

$constructionStyle = match (true) {
$constructor instanceof ReflectionMethod => $constructor->isConstructor() ? 'new' : 'static',
$constructor instanceof \ReflectionMethod => $constructor->isConstructor() ? 'new' : 'static',
! $reflectionClass->isInstantiable() => 'none',
default => 'new',
};
Expand All @@ -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();
Expand All @@ -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];
}

Expand Down Expand Up @@ -139,50 +122,55 @@ 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;
}
}

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(
Expand All @@ -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;
}

Expand All @@ -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);
Expand All @@ -239,7 +227,7 @@ public function provideSerializationDefinition(string $className): ClassSerializ
}

/**
* @param ReflectionAttribute[] $attributes
* @param \ReflectionAttribute[] $attributes
*/
private function typeSpecifier(array $attributes): ?MapToType
{
Expand All @@ -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;
}

Expand All @@ -274,19 +262,19 @@ public function allSerializers(): array
}

/**
* @param ReflectionAttribute[] $attributes
* @param \ReflectionAttribute[] $attributes
*
* @return array<string, array{0: class-string<PropertySerializer>, 1: array<mixed>}>|array<array{0: class-string<PropertySerializer>, 1: array<mixed>}>
*/
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())];
}

Expand All @@ -301,7 +289,7 @@ private function resolveSerializers(ReflectionUnionType|ReflectionNamedType $typ
}

/**
* @param ReflectionAttribute[] $attributes
* @param \ReflectionAttribute[] $attributes
*/
private function resolveSerializersFromAttributes(array $attributes): array
{
Expand All @@ -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()];
}
}
Expand All @@ -324,7 +312,7 @@ public function hasSerializerFor(string $name): bool
}

/**
* @param ReflectionAttribute[] $attributes
* @param \ReflectionAttribute[] $attributes
*
* @return array<string, array<string>>
*/
Expand All @@ -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
{
Expand All @@ -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);

Expand All @@ -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);

Expand Down
1 change: 1 addition & 0 deletions src/Issue75/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
*Hydrator.php
12 changes: 12 additions & 0 deletions src/Issue75/Amount.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<?php
declare(strict_types=1);

namespace EventSauce\ObjectHydrator\Issue75;

class Amount
{
public function __construct(
public int $amount,
) {
}
}
47 changes: 47 additions & 0 deletions src/Issue75/Issue75Test.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
<?php

declare(strict_types=1);

namespace EventSauce\ObjectHydrator\Issue75;

use EventSauce\ObjectHydrator\ObjectMapperCodeGenerator;
use League\ConstructFinder\ConstructFinder;
use PHPUnit\Framework\TestCase;

class Issue75Test extends TestCase
{
/**
* @test
*/
public function recursing_with_hydration_and_serialization(): void
{
$classes = ConstructFinder::locatedIn(__DIR__)
->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);
}
}
Loading
Loading