diff --git a/src/Annotation/ArrayType.php b/src/Annotation/ArrayType.php index f9d342d..cbf48f3 100644 --- a/src/Annotation/ArrayType.php +++ b/src/Annotation/ArrayType.php @@ -3,18 +3,7 @@ namespace SergiX44\Hydrator\Annotation; use Attribute; -use ReflectionClass; -/** - * @Annotation - * @Target({"PROPERTY"}) - * @NamedArgumentConstructor - * - * @Attributes({ - * - * @Attribute("class", type="class-string", required=true), - * }) - */ #[Attribute(Attribute::TARGET_PROPERTY)] final class ArrayType { @@ -40,18 +29,4 @@ public function __construct(string $class, int $depth = 1) $this->class = $class; $this->depth = $depth; } - - /** - * @throws \ReflectionException - */ - public function getInstance() - { - $class = new ReflectionClass($this->class); - - if ($class->getConstructor()?->getNumberOfRequiredParameters() > 0) { - return $class->newInstanceWithoutConstructor(); - } - - return $class->newInstance(); - } } diff --git a/src/Hydrator.php b/src/Hydrator.php index 750b5fd..b53cbb5 100644 --- a/src/Hydrator.php +++ b/src/Hydrator.php @@ -115,7 +115,7 @@ public function hydrate(string|object $object, array|object $data): object ReflectionAttribute::IS_INSTANCEOF ); if (isset($resolver)) { - $propertyType = $resolver->resolve($propertyType, $data[$key]); + $propertyType = $resolver->resolve($propertyType, is_array($data[$key]) ? $data[$key] : $data); } else { throw new Exception\UnsupportedPropertyTypeException(sprintf( 'The %s.%s property cannot be hydrated automatically. Please define an union type resolver attribute or remove the union type.', @@ -587,37 +587,35 @@ private function propertyArray( $arrayType = $this->getAttributeInstance($property, ArrayType::class); if ($arrayType !== null) { - $value = $this->hydrateObjectsInArray($value, $arrayType, $arrayType->depth); + $value = $this->hydrateObjectsInArray($value, $arrayType->class, $arrayType->depth); } $property->setValue($object, $value); } /** - * @param array $array - * @param ArrayType $arrayType - * @param int $depth + * @param array $array + * @param string $class + * @param int $depth * * @throws \ReflectionException * * @return array */ - private function hydrateObjectsInArray(array $array, ArrayType $arrayType, int $depth): array + private function hydrateObjectsInArray(array $array, string $class, int $depth): array { if ($depth > 1) { - return array_map(function ($child) use ($arrayType, $depth) { - return $this->hydrateObjectsInArray($child, $arrayType, --$depth); + return array_map(function ($child) use ($class, $depth) { + return $this->hydrateObjectsInArray($child, $class, --$depth); }, $array); } - return array_map(function ($object) use ($arrayType) { - if (is_subclass_of($arrayType->class, BackedEnum::class)) { - return $arrayType->class::tryFrom($object); + return array_map(function ($object) use ($class) { + if (is_subclass_of($class, BackedEnum::class)) { + return $class::tryFrom($object) ?? $object; } - $newInstance = $this->initializeObject($arrayType->class, $object); - - return $this->hydrate($newInstance, $object); + return $this->hydrate($class, $object); }, $array); } diff --git a/src/Resolver/EnumOrScalar.php b/src/Resolver/EnumOrScalar.php new file mode 100644 index 0000000..a75b30e --- /dev/null +++ b/src/Resolver/EnumOrScalar.php @@ -0,0 +1,63 @@ +getTypes(); + $enum = array_shift($types); + + if (empty($types)) { + return $enum; + } + + $enumClass = $enum->getName(); + + if (!is_subclass_of($enumClass, BackedEnum::class)) { + throw new UnsupportedPropertyTypeException( + sprintf( + 'The enum must be the first type of the union, %s given.', + $enumClass + ) + ); + } + + $value = array_shift($data); + if ((is_string($value) || is_int($value)) && $enumClass::tryFrom($value) !== null) { + return $enum; + } + + $valueType = gettype($value); + $valueType = match ($valueType) { + 'integer' => 'int', + 'double' => 'float', + 'boolean' => 'bool', + default => $valueType, + }; + + foreach ($types as $t) { + if ($t->getName() === $valueType) { + return $t; + } + } + + throw new UnsupportedPropertyTypeException( + sprintf( + 'This property can be %s or %s, %s given.', + $enumClass, + implode(' or ', $types), + $valueType + ) + ); + } +} diff --git a/tests/Fixtures/ObjectWithArrayOfStringableEnum.php b/tests/Fixtures/ObjectWithArrayOfStringableEnum.php new file mode 100644 index 0000000..b8cfcab --- /dev/null +++ b/tests/Fixtures/ObjectWithArrayOfStringableEnum.php @@ -0,0 +1,11 @@ +hydrate(Fixtures\ObjectWithDateInterval::class, ['value' => 'foo']); } + public function testHydrateArrayOfStringableEnumProperty(): void + { + $object = (new Hydrator())->hydrate(Fixtures\ObjectWithArrayOfStringableEnum::class, ['value' => [ + 'c1200a7e-136e-4a11-9bc3-cc937046e90f', + 'a2b29b37-1c5a-4b36-9981-097ddd25c740', + 'c1ea3762-9827-4c0c-808b-53be3febae6d', + ]]); + + $this->assertSame([ + Fixtures\StringableEnum::foo, + Fixtures\StringableEnum::bar, + Fixtures\StringableEnum::baz, + ], $object->value); + } + + public function testHydrateArrayOfStringableEnumPropertyWithoutMatchingEnum(): void + { + $object = (new Hydrator())->hydrate(Fixtures\ObjectWithArrayOfStringableEnum::class, ['value' => [ + 'c1200a7e-136e-4a11-9bc3-cc937046e90f', + 'a2b29b37-1c5a-4b36-9981-097ddd25c740', + 'c1ea3762-9827-4c0c-808b-53be3febae6d', + 'bbb', + ]]); + + $this->assertSame([ + Fixtures\StringableEnum::foo, + Fixtures\StringableEnum::bar, + Fixtures\StringableEnum::baz, + 'bbb', + ], $object->value); + } + + public function testHydrateStringableEnumUnionPropertyString(): void + { + $object = (new Hydrator())->hydrate(Fixtures\ObjectWithStringableEnumUnion::class, ['value' => 'bbb']); + + $this->assertSame('bbb', $object->value); + } + + public function testHydrateStringableEnumUnionPropertyInt(): void + { + $object = (new Hydrator())->hydrate(Fixtures\ObjectWithStringableEnumUnion::class, ['value' => 123]); + + $this->assertSame(123, $object->value); + } + + public function testHydrateStringableEnumUnionPropertyFloat(): void + { + $object = (new Hydrator())->hydrate(Fixtures\ObjectWithStringableEnumUnion::class, ['value' => .23]); + + $this->assertSame(.23, $object->value); + } + + public function testHydrateStringableEnumUnionPropertyBool(): void + { + $this->expectException(Exception\UnsupportedPropertyTypeException::class); + (new Hydrator())->hydrate(Fixtures\ObjectWithStringableEnumUnion::class, ['value' => false]); + } + /** * @dataProvider stringableEnumValueProvider */