From ba88d371bb6e4f18163081d752299675769a0078 Mon Sep 17 00:00:00 2001 From: Sandro Gehri Date: Thu, 22 May 2025 15:52:09 +0200 Subject: [PATCH 1/2] Enhance serialization support for class interfaces in SelfSerializingNormalizer --- .../SelfSerializingNormalizer.php | 4 +++ tests/Feature/SerializationTest.php | 25 ++++++++++++++++++- 2 files changed, 28 insertions(+), 1 deletion(-) diff --git a/src/Support/Normalization/SelfSerializingNormalizer.php b/src/Support/Normalization/SelfSerializingNormalizer.php index 1c66aab3..f845ebf0 100644 --- a/src/Support/Normalization/SelfSerializingNormalizer.php +++ b/src/Support/Normalization/SelfSerializingNormalizer.php @@ -28,6 +28,10 @@ public function denormalize(mixed $data, string $type, ?string $format = null, a return $data; } + if (interface_exists($type) && isset($data['fqcn']) && is_a($data['fqcn'], SerializedByVerbs::class, true)) { + $type = $data['fqcn']; + } + return $type::deserializeForVerbs($data, $this->serializer); } diff --git a/tests/Feature/SerializationTest.php b/tests/Feature/SerializationTest.php index 9f6ccb09..15071796 100644 --- a/tests/Feature/SerializationTest.php +++ b/tests/Feature/SerializationTest.php @@ -169,6 +169,20 @@ public function __construct() ->and($deserialized_event->dtos[0])->toBeInstanceOf(DTO::class); }); +it('allows us to store a serializable class interface as a property', function () { + $original_event = new EventWithDtoInterface; + + $serialized_data = app(Serializer::class)->serialize($original_event); + + expect($serialized_data)->toBe('{"dto":{"fqcn":"DTO","foo":1}}'); + + $deserialized_event = app(Serializer::class)->deserialize(EventWithDtoInterface::class, $serialized_data); + + expect($deserialized_event->dto) + ->toBeInstanceOf(DTO::class) + ->foo->toBe(1); +}); + class EventWithConstructorPromotion extends Event { public function __construct( @@ -187,7 +201,9 @@ class EventWithJustPublicProperties extends Event public string $string; } -class DTO implements SerializedByVerbs +interface DTOInterface extends SerializedByVerbs {} + +class DTO implements DTOInterface { use NormalizeToPropertiesAndClassName; @@ -199,6 +215,13 @@ class EventWithDto extends Event public DTO $dto; } +class EventWithDtoInterface extends Event +{ + public function __construct( + public DTOInterface $dto = new DTO, + ) {} +} + class EventWithConstructor extends Event { public bool $constructed = false; From 98a67f25ef9f238978ac4f9fb081b2c43a0c6981 Mon Sep 17 00:00:00 2001 From: Sandro Gehri Date: Thu, 22 May 2025 22:20:42 +0200 Subject: [PATCH 2/2] Enhance serialization further to support union types --- .../SelfSerializingNormalizer.php | 2 +- tests/Feature/SerializationTest.php | 40 ++++++++++++++++--- 2 files changed, 35 insertions(+), 7 deletions(-) diff --git a/src/Support/Normalization/SelfSerializingNormalizer.php b/src/Support/Normalization/SelfSerializingNormalizer.php index f845ebf0..56eb2eb5 100644 --- a/src/Support/Normalization/SelfSerializingNormalizer.php +++ b/src/Support/Normalization/SelfSerializingNormalizer.php @@ -28,7 +28,7 @@ public function denormalize(mixed $data, string $type, ?string $format = null, a return $data; } - if (interface_exists($type) && isset($data['fqcn']) && is_a($data['fqcn'], SerializedByVerbs::class, true)) { + if (isset($data['fqcn']) && is_a($data['fqcn'], SerializedByVerbs::class, true)) { $type = $data['fqcn']; } diff --git a/tests/Feature/SerializationTest.php b/tests/Feature/SerializationTest.php index 15071796..e4f682ad 100644 --- a/tests/Feature/SerializationTest.php +++ b/tests/Feature/SerializationTest.php @@ -174,13 +174,27 @@ public function __construct() $serialized_data = app(Serializer::class)->serialize($original_event); - expect($serialized_data)->toBe('{"dto":{"fqcn":"DTO","foo":1}}'); + expect($serialized_data)->toBe('{"dto":{"fqcn":"OtherDTO","bar":2}}'); $deserialized_event = app(Serializer::class)->deserialize(EventWithDtoInterface::class, $serialized_data); expect($deserialized_event->dto) - ->toBeInstanceOf(DTO::class) - ->foo->toBe(1); + ->toBeInstanceOf(OtherDTO::class) + ->bar->toBe(2); +}); + +it('allows us to store a serializable class as a union type property', function () { + $original_event = new EventWithUnionType; + + $serialized_data = app(Serializer::class)->serialize($original_event); + + expect($serialized_data)->toBe('{"dto":{"fqcn":"OtherDTO","bar":2}}'); + + $deserialized_event = app(Serializer::class)->deserialize(EventWithUnionType::class, $serialized_data); + + expect($deserialized_event->dto) + ->toBeInstanceOf(OtherDTO::class) + ->bar->toBe(2); }); class EventWithConstructorPromotion extends Event @@ -201,13 +215,20 @@ class EventWithJustPublicProperties extends Event public string $string; } +class DTO implements SerializedByVerbs +{ + use NormalizeToPropertiesAndClassName; + + public int $foo = 1; +} + interface DTOInterface extends SerializedByVerbs {} -class DTO implements DTOInterface +class OtherDTO implements DTOInterface { use NormalizeToPropertiesAndClassName; - public int $foo = 1; + public int $bar = 2; } class EventWithDto extends Event @@ -218,7 +239,14 @@ class EventWithDto extends Event class EventWithDtoInterface extends Event { public function __construct( - public DTOInterface $dto = new DTO, + public DTOInterface $dto = new OtherDTO, + ) {} +} + +class EventWithUnionType extends Event +{ + public function __construct( + public DTO|OtherDTO $dto = new OtherDTO, ) {} }