Skip to content

Commit 98e7a41

Browse files
authored
Merge pull request #24 from nutgram/fix-array-of-enum
Bump to PHP 8.1 + Add SkipConstructor attribute + Fix enum hydrate in arrays
2 parents ab1734f + 31dae94 commit 98e7a41

File tree

5 files changed

+129
-67
lines changed

5 files changed

+129
-67
lines changed

composer.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,12 +24,12 @@
2424
}
2525
],
2626
"require": {
27-
"php": "^8.0",
27+
"php": "^8.1",
2828
"psr/container": "^1.1 || ^2.0"
2929
},
3030
"require-dev": {
3131
"phpunit/phpunit": "~9.5.0",
32-
"league/container": "^4.2"
32+
"illuminate/container": "^10.0"
3333
},
3434
"autoload": {
3535
"psr-4": {

src/Annotation/SkipConstructor.php

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
<?php
2+
3+
namespace SergiX44\Hydrator\Annotation;
4+
5+
use Attribute;
6+
7+
/**
8+
* @Annotation
9+
*
10+
* @Target({"CLASS"})
11+
*/
12+
#[Attribute(Attribute::TARGET_CLASS)]
13+
final class SkipConstructor
14+
{
15+
}

src/Hydrator.php

Lines changed: 64 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
use SergiX44\Hydrator\Annotation\Alias;
1919
use SergiX44\Hydrator\Annotation\ArrayType;
2020
use SergiX44\Hydrator\Annotation\ConcreteResolver;
21+
use SergiX44\Hydrator\Annotation\SkipConstructor;
2122
use SergiX44\Hydrator\Annotation\UnionResolver;
2223
use SergiX44\Hydrator\Exception\InvalidObjectException;
2324

@@ -57,8 +58,6 @@ public function __construct(?ContainerInterface $container = null)
5758
* @param class-string<T>|T $object
5859
* @param array|object $data
5960
*
60-
* @throws Exception\UntypedPropertyException
61-
* If one of the object properties isn't typed.
6261
* @throws Exception\UnsupportedPropertyTypeException
6362
* If one of the object properties contains an unsupported type.
6463
* @throws Exception\MissingRequiredValueException
@@ -69,6 +68,8 @@ public function __construct(?ContainerInterface $container = null)
6968
* If the object cannot be hydrated.
7069
* @throws InvalidArgumentException
7170
* If the data isn't valid.
71+
* @throws Exception\UntypedPropertyException
72+
* If one of the object properties isn't typed.
7273
*
7374
* @return T
7475
*
@@ -109,7 +110,11 @@ public function hydrate(string|object $object, array|object $data): object
109110
}
110111

111112
if ($propertyType instanceof ReflectionUnionType) {
112-
$resolver = $this->getAttributeInstance($property, UnionResolver::class, ReflectionAttribute::IS_INSTANCEOF);
113+
$resolver = $this->getAttributeInstance(
114+
$property,
115+
UnionResolver::class,
116+
ReflectionAttribute::IS_INSTANCEOF
117+
);
113118
if (isset($resolver)) {
114119
$propertyType = $resolver->resolve($propertyType, $data[$key]);
115120
} else {
@@ -146,10 +151,10 @@ public function hydrate(string|object $object, array|object $data): object
146151
* @param string $json
147152
* @param ?int $flags
148153
*
149-
* @throws Exception\HydrationException
150-
* If the object cannot be hydrated.
151154
* @throws InvalidArgumentException
152155
* If the JSON cannot be decoded.
156+
* @throws Exception\HydrationException
157+
* If the object cannot be hydrated.
153158
*
154159
* @return T
155160
*
@@ -182,17 +187,21 @@ public function hydrateWithJson(string|object $object, string $json, ?int $flags
182187
*/
183188
public function getConcreteResolverFor(string|object $object): ?ConcreteResolver
184189
{
185-
return $this->getAttributeInstance(new ReflectionClass($object), ConcreteResolver::class, ReflectionAttribute::IS_INSTANCEOF);
190+
return $this->getAttributeInstance(
191+
new ReflectionClass($object),
192+
ConcreteResolver::class,
193+
ReflectionAttribute::IS_INSTANCEOF
194+
);
186195
}
187196

188197
/**
189198
* Initializes the given object.
190199
*
191200
* @param class-string<T>|T $object
192201
*
193-
* @throws InvalidArgumentException
194202
* @throws ContainerExceptionInterface
195203
* If the object cannot be initialized.
204+
* @throws InvalidArgumentException
196205
*
197206
* @return T
198207
*
@@ -214,7 +223,11 @@ private function initializeObject(string|object $object, array|object $data): ob
214223
$reflectionClass = new ReflectionClass($object);
215224

216225
if ($reflectionClass->isAbstract()) {
217-
$attribute = $this->getAttributeInstance($reflectionClass, ConcreteResolver::class, ReflectionAttribute::IS_INSTANCEOF);
226+
$attribute = $this->getAttributeInstance(
227+
$reflectionClass,
228+
ConcreteResolver::class,
229+
ReflectionAttribute::IS_INSTANCEOF
230+
);
218231

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

229242
// if we have a container, get the instance through it
243+
$skipConstructor = $this->getAttributeInstance($reflectionClass, SkipConstructor::class);
244+
if ($skipConstructor !== null) {
245+
return $reflectionClass->newInstanceWithoutConstructor();
246+
}
247+
230248
if ($this->container !== null) {
231249
return $this->container->get($object);
232250
}
@@ -254,8 +272,11 @@ private function initializeObject(string|object $object, array|object $data): ob
254272
*
255273
* @return T|null
256274
*/
257-
private function getAttributeInstance(ReflectionProperty|ReflectionClass $target, string $class, int $criteria = 0): mixed
258-
{
275+
private function getAttributeInstance(
276+
ReflectionProperty|ReflectionClass $target,
277+
string $class,
278+
int $criteria = 0
279+
): mixed {
259280
$attributes = $target->getAttributes($class, $criteria);
260281
if (isset($attributes[0])) {
261282
return $attributes[0]->newInstance();
@@ -273,10 +294,10 @@ private function getAttributeInstance(ReflectionProperty|ReflectionClass $target
273294
* @param ReflectionNamedType $type
274295
* @param mixed $value
275296
*
276-
* @throws Exception\InvalidValueException
277-
* If the given value isn't valid.
278297
* @throws Exception\UnsupportedPropertyTypeException
279298
* If the given property contains an unsupported type.
299+
* @throws Exception\InvalidValueException
300+
* If the given value isn't valid.
280301
*
281302
* @return void
282303
*/
@@ -291,7 +312,12 @@ private function hydrateProperty(
291312

292313
match (true) {
293314
// an empty string for a non-string type is always processes as null
294-
'' === $value && 'string' !== $propertyType, null === $value => $this->propertyNull($object, $class, $property, $type),
315+
'' === $value && 'string' !== $propertyType, null === $value => $this->propertyNull(
316+
$object,
317+
$class,
318+
$property,
319+
$type
320+
),
295321

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

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

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

308-
DateTime::class === $propertyType, DateTimeImmutable::class === $propertyType => $this->propertyDateTime($object, $class, $property, $type, $value),
309-
310-
DateInterval::class === $propertyType => $this->propertyDateInterval($object, $class, $property, $type, $value),
311-
312-
PHP_VERSION_ID >= 80100 && is_subclass_of($propertyType, BackedEnum::class) => $this->propertyBackedEnum($object, $class, $property, $type, $value),
334+
DateTime::class === $propertyType, DateTimeImmutable::class === $propertyType => $this->propertyDateTime(
335+
$object,
336+
$class,
337+
$property,
338+
$type,
339+
$value
340+
),
341+
342+
DateInterval::class === $propertyType => $this->propertyDateInterval(
343+
$object,
344+
$class,
345+
$property,
346+
$type,
347+
$value
348+
),
349+
350+
PHP_VERSION_ID >= 80100 && is_subclass_of(
351+
$propertyType,
352+
BackedEnum::class
353+
) => $this->propertyBackedEnum($object, $class, $property, $type, $value),
313354

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

@@ -562,7 +603,11 @@ private function hydrateObjectsInArray(array $array, ArrayType $arrayType, int $
562603
}
563604

564605
return array_map(function ($object) use ($arrayType) {
565-
$newInstance = $this->container?->get($arrayType->class) ?? $arrayType->getInstance();
606+
if (is_subclass_of($arrayType->class, BackedEnum::class)) {
607+
return $arrayType->class::tryFrom($object);
608+
}
609+
610+
$newInstance = $this->initializeObject($arrayType->class, []);
566611

567612
return $this->hydrate($newInstance, $object);
568613
}, $array);
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
<?php
2+
3+
namespace SergiX44\Hydrator\Tests\Fixtures;
4+
5+
use SergiX44\Hydrator\Annotation\ArrayType;
6+
use SergiX44\Hydrator\Annotation\SkipConstructor;
7+
8+
#[SkipConstructor]
9+
final class ObjectWithEnumInConstructor
10+
{
11+
public StringableEnum $stringableEnum;
12+
13+
#[ArrayType(NumerableEnum::class)]
14+
public array $numerableEnums;
15+
16+
public function __construct(StringableEnum $value, array $numerableEnums)
17+
{
18+
$this->stringableEnum = $value;
19+
$this->numerableEnums = $numerableEnums;
20+
}
21+
}

0 commit comments

Comments
 (0)