From 2af95a018e096a760d583ecff8149060afa1f1db Mon Sep 17 00:00:00 2001 From: Sergio Brighenti Date: Fri, 28 Mar 2025 00:55:18 +0100 Subject: [PATCH 1/2] feat: add OverrideConstructor annotation for custom constructor handling --- src/Annotation/OverrideConstructor.php | 30 ++++++++ src/Hydrator.php | 11 ++- tests/Fixtures/Store/Audi.php | 13 ++++ tests/Fixtures/Store/Car.php | 17 +++++ tests/Fixtures/Store/Key.php | 8 +++ tests/HydratorTest.php | 94 +++++++++++++++++--------- 6 files changed, 138 insertions(+), 35 deletions(-) create mode 100644 src/Annotation/OverrideConstructor.php create mode 100644 tests/Fixtures/Store/Audi.php create mode 100644 tests/Fixtures/Store/Car.php create mode 100644 tests/Fixtures/Store/Key.php diff --git a/src/Annotation/OverrideConstructor.php b/src/Annotation/OverrideConstructor.php new file mode 100644 index 0000000..837ded9 --- /dev/null +++ b/src/Annotation/OverrideConstructor.php @@ -0,0 +1,30 @@ +method); + + return array_map( + static fn($parameter) => $container->get($parameter->getType()?->getName()), + $method->getParameters() + ); + } +} diff --git a/src/Hydrator.php b/src/Hydrator.php index 91687b9..b4c8f51 100644 --- a/src/Hydrator.php +++ b/src/Hydrator.php @@ -19,10 +19,10 @@ use SergiX44\Hydrator\Annotation\ArrayType; use SergiX44\Hydrator\Annotation\ConcreteResolver; use SergiX44\Hydrator\Annotation\Mutate; +use SergiX44\Hydrator\Annotation\OverrideConstructor; use SergiX44\Hydrator\Annotation\SkipConstructor; use SergiX44\Hydrator\Annotation\UnionResolver; use SergiX44\Hydrator\Exception\InvalidObjectException; - use function array_key_exists; use function class_exists; use function ctype_digit; @@ -38,7 +38,6 @@ use function is_subclass_of; use function sprintf; use function strtotime; - use const FILTER_NULL_ON_FAILURE; use const FILTER_VALIDATE_BOOLEAN; use const FILTER_VALIDATE_FLOAT; @@ -266,6 +265,14 @@ private function initializeObject(string|object $object, array|object $data): ob } if ($this->container !== null) { + $overrideConstructor = $this->getAttributeInstance($reflectionClass, OverrideConstructor::class); + if ($overrideConstructor !== null) { + $obj = $reflectionClass->newInstanceWithoutConstructor(); + $args = $overrideConstructor->getArguments($obj, $this->container); + call_user_func([$obj, $overrideConstructor->method], ...$args); + return $obj; + } + return $this->container->get($object); } diff --git a/tests/Fixtures/Store/Audi.php b/tests/Fixtures/Store/Audi.php new file mode 100644 index 0000000..ea2ed4a --- /dev/null +++ b/tests/Fixtures/Store/Audi.php @@ -0,0 +1,13 @@ +key = $key; + + return $this; + } +} diff --git a/tests/Fixtures/Store/Key.php b/tests/Fixtures/Store/Key.php new file mode 100644 index 0000000..d0aba74 --- /dev/null +++ b/tests/Fixtures/Store/Key.php @@ -0,0 +1,8 @@ +hydrate(Fixtures\ObjectWithUnionAndAttribute::class, [ 'tag' => [ - 'name' => 'foo', + 'name' => 'foo', 'price' => 1.00, ], ]); @@ -490,11 +492,13 @@ public function testHydrateDateIntervalPropertyWithInvalidFormat(): void 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', - ]]); + $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, @@ -505,12 +509,14 @@ public function testHydrateArrayOfStringableEnumProperty(): void 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', - ]]); + $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, @@ -784,9 +790,9 @@ public function testHydrateProductWithJsonAsObject(): void public function testHydrateAbstractObject(): void { $o = (new Hydrator())->hydrate(Apple::class, [ - 'type' => 'sauce', + 'type' => 'sauce', 'sweetness' => 100, - 'category' => null, + 'category' => null, ]); $this->assertInstanceOf(AppleSauce::class, $o); @@ -813,9 +819,9 @@ public function testHydrateAbstractProperty(): void { $o = (new Hydrator())->hydrate(new ObjectWithAbstract(), [ 'value' => [ - 'type' => 'jack', + 'type' => 'jack', 'sweetness' => null, - 'category' => 'brandy', + 'category' => 'brandy', ], ]); @@ -828,11 +834,13 @@ public function testHydrateAbstractProperty(): void public function testHydrateArrayAbstractProperty(): void { $o = (new Hydrator())->hydrate(new ObjectWithArrayOfAbstracts(), [ - 'value' => [[ - 'type' => 'jack', - 'sweetness' => null, - 'category' => 'brandy', - ]], + 'value' => [ + [ + 'type' => 'jack', + 'sweetness' => null, + 'category' => 'brandy', + ], + ], ]); $this->assertInstanceOf(ObjectWithArrayOfAbstracts::class, $o); @@ -848,11 +856,13 @@ public function testHydrateArrayAbstractProperty(): void public function testHydrateArrayAbstractPropertyWithObject(): void { $o = (new Hydrator())->hydrate(new ObjectWithArrayOfAbstracts(), [ - 'value' => [(object) [ - 'type' => 'jack', - 'sweetness' => null, - 'category' => 'brandy', - ]], + 'value' => [ + (object) [ + 'type' => 'jack', + 'sweetness' => null, + 'category' => 'brandy', + ], + ], ]); $this->assertInstanceOf(ObjectWithArrayOfAbstracts::class, $o); @@ -886,7 +896,7 @@ public function testHydrateWithContainer(): void $hydrator = new Hydrator($container); $o = $hydrator->hydrate(Tree::class, [ - 'name' => 'foo', + 'name' => 'foo', 'leaves' => [ 'n' => 100, ], @@ -918,7 +928,7 @@ public function testHydrateWithContainerWithNestedInstances(): void $o = $hydrator->hydrate(Forest::class, [ 'trees' => [ [ - 'name' => 'foo', + 'name' => 'foo', 'leaves' => [ 'n' => 100, ], @@ -927,7 +937,7 @@ public function testHydrateWithContainerWithNestedInstances(): void ], ], [ - 'name' => 'foo2', + 'name' => 'foo2', 'leaves' => [ 'n' => 200, ], @@ -993,7 +1003,7 @@ public function testSkipConstructorWithContainer(): void public function testMutateProperty(): void { $object = (new Hydrator())->hydrate(Fixtures\ObjectWithArrayToDeserialize::class, [ - 'name' => 'foo', + 'name' => 'foo', 'value' => json_encode(['foo' => 'bar'], JSON_THROW_ON_ERROR), ]); @@ -1005,9 +1015,9 @@ public function testMutateProperty(): void public function testHydrateAdditionalWithMagicMethod() { $object = (new Hydrator())->hydrate(Fixtures\ObjectWithMagicSet::class, [ - 'name' => 'foo', - 'value' => 'bar', - 'type' => false, + 'name' => 'foo', + 'value' => 'bar', + 'type' => false, 'number' => 42, ]); @@ -1016,4 +1026,22 @@ public function testHydrateAdditionalWithMagicMethod() $this->assertFalse($object->type); $this->assertSame(42, $object->number); } + + public function testHydrateWithOverrideConstructor() + { + $container = new Container(); + $key = new Key(); + $container->bind(Key::class, function () use ($key) { + return $key; + }); + + $object = (new Hydrator($container))->hydrate(Audi::class, [ + 'model' => 'A4', + 'year' => 2021, + ]); + + $this->assertSame('A4', $object->model); + $this->assertSame(2021, $object->year); + $this->assertSame($key, $object->key); + } } From eff7a45cacb5d8b58f7a8f11291c226fdd2fd184 Mon Sep 17 00:00:00 2001 From: Luca Patera Date: Thu, 27 Mar 2025 23:55:32 +0000 Subject: [PATCH 2/2] Apply fixes from StyleCI [ci skip] [skip ci] --- src/Annotation/OverrideConstructor.php | 3 +-- src/Hydrator.php | 3 +++ tests/Fixtures/DI/Leaves.php | 2 +- tests/Fixtures/DI/Tree.php | 2 +- tests/Fixtures/DI/Wood.php | 2 +- tests/HydratorTest.php | 34 +++++++++++++------------- 6 files changed, 24 insertions(+), 22 deletions(-) diff --git a/src/Annotation/OverrideConstructor.php b/src/Annotation/OverrideConstructor.php index 837ded9..1cef98b 100644 --- a/src/Annotation/OverrideConstructor.php +++ b/src/Annotation/OverrideConstructor.php @@ -17,13 +17,12 @@ public function __construct(public string $method) { } - public function getArguments(mixed $object, ContainerInterface $container): array { $method = new ReflectionMethod($object, $this->method); return array_map( - static fn($parameter) => $container->get($parameter->getType()?->getName()), + static fn ($parameter) => $container->get($parameter->getType()?->getName()), $method->getParameters() ); } diff --git a/src/Hydrator.php b/src/Hydrator.php index b4c8f51..5c78d4d 100644 --- a/src/Hydrator.php +++ b/src/Hydrator.php @@ -23,6 +23,7 @@ use SergiX44\Hydrator\Annotation\SkipConstructor; use SergiX44\Hydrator\Annotation\UnionResolver; use SergiX44\Hydrator\Exception\InvalidObjectException; + use function array_key_exists; use function class_exists; use function ctype_digit; @@ -38,6 +39,7 @@ use function is_subclass_of; use function sprintf; use function strtotime; + use const FILTER_NULL_ON_FAILURE; use const FILTER_VALIDATE_BOOLEAN; use const FILTER_VALIDATE_FLOAT; @@ -270,6 +272,7 @@ private function initializeObject(string|object $object, array|object $data): ob $obj = $reflectionClass->newInstanceWithoutConstructor(); $args = $overrideConstructor->getArguments($obj, $this->container); call_user_func([$obj, $overrideConstructor->method], ...$args); + return $obj; } diff --git a/tests/Fixtures/DI/Leaves.php b/tests/Fixtures/DI/Leaves.php index 704d5f1..45cc9c2 100644 --- a/tests/Fixtures/DI/Leaves.php +++ b/tests/Fixtures/DI/Leaves.php @@ -6,7 +6,7 @@ class Leaves { public int $n; - private Sun|null $sun = null; + private ?Sun $sun = null; public function __construct(?Sun $sun = null) { diff --git a/tests/Fixtures/DI/Tree.php b/tests/Fixtures/DI/Tree.php index 36f2a38..b17638a 100644 --- a/tests/Fixtures/DI/Tree.php +++ b/tests/Fixtures/DI/Tree.php @@ -10,7 +10,7 @@ class Tree public Leaves $leaves; - private Sun|null $sun = null; + private ?Sun $sun = null; public function __construct(?Sun $sun = null) { diff --git a/tests/Fixtures/DI/Wood.php b/tests/Fixtures/DI/Wood.php index 9079dd2..f6ac894 100644 --- a/tests/Fixtures/DI/Wood.php +++ b/tests/Fixtures/DI/Wood.php @@ -6,7 +6,7 @@ class Wood { public int $kg; - private Sun|null $sun = null; + private ?Sun $sun = null; public function __construct(?Sun $sun = null) { diff --git a/tests/HydratorTest.php b/tests/HydratorTest.php index 7d24d49..d01e493 100644 --- a/tests/HydratorTest.php +++ b/tests/HydratorTest.php @@ -106,7 +106,7 @@ public function testAnnotatedUnionPropertyWithTagPriceType(): void $o = (new Hydrator())->hydrate(Fixtures\ObjectWithUnionAndAttribute::class, [ 'tag' => [ - 'name' => 'foo', + 'name' => 'foo', 'price' => 1.00, ], ]); @@ -790,9 +790,9 @@ public function testHydrateProductWithJsonAsObject(): void public function testHydrateAbstractObject(): void { $o = (new Hydrator())->hydrate(Apple::class, [ - 'type' => 'sauce', + 'type' => 'sauce', 'sweetness' => 100, - 'category' => null, + 'category' => null, ]); $this->assertInstanceOf(AppleSauce::class, $o); @@ -819,9 +819,9 @@ public function testHydrateAbstractProperty(): void { $o = (new Hydrator())->hydrate(new ObjectWithAbstract(), [ 'value' => [ - 'type' => 'jack', + 'type' => 'jack', 'sweetness' => null, - 'category' => 'brandy', + 'category' => 'brandy', ], ]); @@ -836,9 +836,9 @@ public function testHydrateArrayAbstractProperty(): void $o = (new Hydrator())->hydrate(new ObjectWithArrayOfAbstracts(), [ 'value' => [ [ - 'type' => 'jack', + 'type' => 'jack', 'sweetness' => null, - 'category' => 'brandy', + 'category' => 'brandy', ], ], ]); @@ -858,9 +858,9 @@ public function testHydrateArrayAbstractPropertyWithObject(): void $o = (new Hydrator())->hydrate(new ObjectWithArrayOfAbstracts(), [ 'value' => [ (object) [ - 'type' => 'jack', + 'type' => 'jack', 'sweetness' => null, - 'category' => 'brandy', + 'category' => 'brandy', ], ], ]); @@ -896,7 +896,7 @@ public function testHydrateWithContainer(): void $hydrator = new Hydrator($container); $o = $hydrator->hydrate(Tree::class, [ - 'name' => 'foo', + 'name' => 'foo', 'leaves' => [ 'n' => 100, ], @@ -928,7 +928,7 @@ public function testHydrateWithContainerWithNestedInstances(): void $o = $hydrator->hydrate(Forest::class, [ 'trees' => [ [ - 'name' => 'foo', + 'name' => 'foo', 'leaves' => [ 'n' => 100, ], @@ -937,7 +937,7 @@ public function testHydrateWithContainerWithNestedInstances(): void ], ], [ - 'name' => 'foo2', + 'name' => 'foo2', 'leaves' => [ 'n' => 200, ], @@ -1003,7 +1003,7 @@ public function testSkipConstructorWithContainer(): void public function testMutateProperty(): void { $object = (new Hydrator())->hydrate(Fixtures\ObjectWithArrayToDeserialize::class, [ - 'name' => 'foo', + 'name' => 'foo', 'value' => json_encode(['foo' => 'bar'], JSON_THROW_ON_ERROR), ]); @@ -1015,9 +1015,9 @@ public function testMutateProperty(): void public function testHydrateAdditionalWithMagicMethod() { $object = (new Hydrator())->hydrate(Fixtures\ObjectWithMagicSet::class, [ - 'name' => 'foo', - 'value' => 'bar', - 'type' => false, + 'name' => 'foo', + 'value' => 'bar', + 'type' => false, 'number' => 42, ]); @@ -1037,7 +1037,7 @@ public function testHydrateWithOverrideConstructor() $object = (new Hydrator($container))->hydrate(Audi::class, [ 'model' => 'A4', - 'year' => 2021, + 'year' => 2021, ]); $this->assertSame('A4', $object->model);