Skip to content

Commit

Permalink
Merge pull request #24 from nutgram/fix-array-of-enum
Browse files Browse the repository at this point in the history
Bump to PHP 8.1 + Add SkipConstructor attribute + Fix enum hydrate in arrays
  • Loading branch information
sergix44 authored Jun 27, 2023
2 parents ab1734f + 31dae94 commit 98e7a41
Show file tree
Hide file tree
Showing 5 changed files with 129 additions and 67 deletions.
4 changes: 2 additions & 2 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,12 @@
}
],
"require": {
"php": "^8.0",
"php": "^8.1",
"psr/container": "^1.1 || ^2.0"
},
"require-dev": {
"phpunit/phpunit": "~9.5.0",
"league/container": "^4.2"
"illuminate/container": "^10.0"
},
"autoload": {
"psr-4": {
Expand Down
15 changes: 15 additions & 0 deletions src/Annotation/SkipConstructor.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<?php

namespace SergiX44\Hydrator\Annotation;

use Attribute;

/**
* @Annotation
*
* @Target({"CLASS"})
*/
#[Attribute(Attribute::TARGET_CLASS)]
final class SkipConstructor
{
}
83 changes: 64 additions & 19 deletions src/Hydrator.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
use SergiX44\Hydrator\Annotation\Alias;
use SergiX44\Hydrator\Annotation\ArrayType;
use SergiX44\Hydrator\Annotation\ConcreteResolver;
use SergiX44\Hydrator\Annotation\SkipConstructor;
use SergiX44\Hydrator\Annotation\UnionResolver;
use SergiX44\Hydrator\Exception\InvalidObjectException;

Expand Down Expand Up @@ -57,8 +58,6 @@ public function __construct(?ContainerInterface $container = null)
* @param class-string<T>|T $object
* @param array|object $data
*
* @throws Exception\UntypedPropertyException
* If one of the object properties isn't typed.
* @throws Exception\UnsupportedPropertyTypeException
* If one of the object properties contains an unsupported type.
* @throws Exception\MissingRequiredValueException
Expand All @@ -69,6 +68,8 @@ public function __construct(?ContainerInterface $container = null)
* If the object cannot be hydrated.
* @throws InvalidArgumentException
* If the data isn't valid.
* @throws Exception\UntypedPropertyException
* If one of the object properties isn't typed.
*
* @return T
*
Expand Down Expand Up @@ -109,7 +110,11 @@ public function hydrate(string|object $object, array|object $data): object
}

if ($propertyType instanceof ReflectionUnionType) {
$resolver = $this->getAttributeInstance($property, UnionResolver::class, ReflectionAttribute::IS_INSTANCEOF);
$resolver = $this->getAttributeInstance(
$property,
UnionResolver::class,
ReflectionAttribute::IS_INSTANCEOF
);
if (isset($resolver)) {
$propertyType = $resolver->resolve($propertyType, $data[$key]);
} else {
Expand Down Expand Up @@ -146,10 +151,10 @@ public function hydrate(string|object $object, array|object $data): object
* @param string $json
* @param ?int $flags
*
* @throws Exception\HydrationException
* If the object cannot be hydrated.
* @throws InvalidArgumentException
* If the JSON cannot be decoded.
* @throws Exception\HydrationException
* If the object cannot be hydrated.
*
* @return T
*
Expand Down Expand Up @@ -182,17 +187,21 @@ public function hydrateWithJson(string|object $object, string $json, ?int $flags
*/
public function getConcreteResolverFor(string|object $object): ?ConcreteResolver
{
return $this->getAttributeInstance(new ReflectionClass($object), ConcreteResolver::class, ReflectionAttribute::IS_INSTANCEOF);
return $this->getAttributeInstance(
new ReflectionClass($object),
ConcreteResolver::class,
ReflectionAttribute::IS_INSTANCEOF
);
}

/**
* Initializes the given object.
*
* @param class-string<T>|T $object
*
* @throws InvalidArgumentException
* @throws ContainerExceptionInterface
* If the object cannot be initialized.
* @throws InvalidArgumentException
*
* @return T
*
Expand All @@ -214,7 +223,11 @@ private function initializeObject(string|object $object, array|object $data): ob
$reflectionClass = new ReflectionClass($object);

if ($reflectionClass->isAbstract()) {
$attribute = $this->getAttributeInstance($reflectionClass, ConcreteResolver::class, ReflectionAttribute::IS_INSTANCEOF);
$attribute = $this->getAttributeInstance(
$reflectionClass,
ConcreteResolver::class,
ReflectionAttribute::IS_INSTANCEOF
);

if ($attribute === null) {
throw new InvalidObjectException(sprintf(
Expand All @@ -227,6 +240,11 @@ private function initializeObject(string|object $object, array|object $data): ob
}

// if we have a container, get the instance through it
$skipConstructor = $this->getAttributeInstance($reflectionClass, SkipConstructor::class);
if ($skipConstructor !== null) {
return $reflectionClass->newInstanceWithoutConstructor();
}

if ($this->container !== null) {
return $this->container->get($object);
}
Expand Down Expand Up @@ -254,8 +272,11 @@ private function initializeObject(string|object $object, array|object $data): ob
*
* @return T|null
*/
private function getAttributeInstance(ReflectionProperty|ReflectionClass $target, string $class, int $criteria = 0): mixed
{
private function getAttributeInstance(
ReflectionProperty|ReflectionClass $target,
string $class,
int $criteria = 0
): mixed {
$attributes = $target->getAttributes($class, $criteria);
if (isset($attributes[0])) {
return $attributes[0]->newInstance();
Expand All @@ -273,10 +294,10 @@ private function getAttributeInstance(ReflectionProperty|ReflectionClass $target
* @param ReflectionNamedType $type
* @param mixed $value
*
* @throws Exception\InvalidValueException
* If the given value isn't valid.
* @throws Exception\UnsupportedPropertyTypeException
* If the given property contains an unsupported type.
* @throws Exception\InvalidValueException
* If the given value isn't valid.
*
* @return void
*/
Expand All @@ -291,7 +312,12 @@ private function hydrateProperty(

match (true) {
// an empty string for a non-string type is always processes as null
'' === $value && 'string' !== $propertyType, null === $value => $this->propertyNull($object, $class, $property, $type),
'' === $value && 'string' !== $propertyType, null === $value => $this->propertyNull(
$object,
$class,
$property,
$type
),

'bool' === $propertyType => $this->propertyBool($object, $class, $property, $type, $value),

Expand All @@ -305,11 +331,26 @@ private function hydrateProperty(

'object' === $propertyType => $this->propertyObject($object, $class, $property, $type, $value),

DateTime::class === $propertyType, DateTimeImmutable::class === $propertyType => $this->propertyDateTime($object, $class, $property, $type, $value),

DateInterval::class === $propertyType => $this->propertyDateInterval($object, $class, $property, $type, $value),

PHP_VERSION_ID >= 80100 && is_subclass_of($propertyType, BackedEnum::class) => $this->propertyBackedEnum($object, $class, $property, $type, $value),
DateTime::class === $propertyType, DateTimeImmutable::class === $propertyType => $this->propertyDateTime(
$object,
$class,
$property,
$type,
$value
),

DateInterval::class === $propertyType => $this->propertyDateInterval(
$object,
$class,
$property,
$type,
$value
),

PHP_VERSION_ID >= 80100 && is_subclass_of(
$propertyType,
BackedEnum::class
) => $this->propertyBackedEnum($object, $class, $property, $type, $value),

class_exists($propertyType) => $this->propertyFromInstance($object, $class, $property, $type, $value),

Expand Down Expand Up @@ -562,7 +603,11 @@ private function hydrateObjectsInArray(array $array, ArrayType $arrayType, int $
}

return array_map(function ($object) use ($arrayType) {
$newInstance = $this->container?->get($arrayType->class) ?? $arrayType->getInstance();
if (is_subclass_of($arrayType->class, BackedEnum::class)) {
return $arrayType->class::tryFrom($object);
}

$newInstance = $this->initializeObject($arrayType->class, []);

return $this->hydrate($newInstance, $object);
}, $array);
Expand Down
21 changes: 21 additions & 0 deletions tests/Fixtures/ObjectWithEnumInConstructor.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<?php

namespace SergiX44\Hydrator\Tests\Fixtures;

use SergiX44\Hydrator\Annotation\ArrayType;
use SergiX44\Hydrator\Annotation\SkipConstructor;

#[SkipConstructor]
final class ObjectWithEnumInConstructor
{
public StringableEnum $stringableEnum;

#[ArrayType(NumerableEnum::class)]
public array $numerableEnums;

public function __construct(StringableEnum $value, array $numerableEnums)
{
$this->stringableEnum = $value;
$this->numerableEnums = $numerableEnums;
}
}
Loading

0 comments on commit 98e7a41

Please sign in to comment.