From 5c2ed3c3c1dda11e75854ca1c96aea53673ef6f5 Mon Sep 17 00:00:00 2001 From: Adam Date: Thu, 19 Dec 2024 13:05:06 -0500 Subject: [PATCH] Allow union types in event properties (#189) * Allow union types in event properties * Add test with union types * Account for ReflectionIntersectionType --------- Co-authored-by: Chris Morrell --- src/Support/EventStateRegistry.php | 38 ++++++++++++++++---- tests/Unit/UseStatesDirectlyInEventsTest.php | 32 +++++++++++++++++ 2 files changed, 63 insertions(+), 7 deletions(-) diff --git a/src/Support/EventStateRegistry.php b/src/Support/EventStateRegistry.php index 3e3eabf4..1a956589 100644 --- a/src/Support/EventStateRegistry.php +++ b/src/Support/EventStateRegistry.php @@ -7,7 +7,10 @@ use InvalidArgumentException; use ReflectionAttribute; use ReflectionClass; +use ReflectionIntersectionType; +use ReflectionNamedType; use ReflectionProperty; +use ReflectionUnionType; use Thunk\Verbs\Attributes\Autodiscovery\StateDiscoveryAttribute; use Thunk\Verbs\Event; use Thunk\Verbs\Lifecycle\StateManager; @@ -20,7 +23,7 @@ class EventStateRegistry protected array $discovered_properties = []; public function __construct( - protected StateManager $manager + protected StateManager $manager, ) {} public function getStates(Event $event): StateCollection @@ -54,7 +57,7 @@ protected function discoverAndPushState(StateDiscoveryAttribute $attribute, Even $states = Arr::wrap( $attribute ->setDiscoveredState($discovered) - ->discoverState($target, $this->manager) + ->discoverState($target, $this->manager), ); $discovered->push(...$states); @@ -120,17 +123,38 @@ protected function findAllProperties(Event $target): Collection return collect($reflect->getProperties(ReflectionProperty::IS_PUBLIC)) ->filter(function (ReflectionProperty $property) use ($target) { - $propertyType = $property->getType(); - $propertyTypeName = $propertyType?->getName(); + $property_type = $property->getType(); - if ($propertyType->allowsNull() && $property->getValue($target) === null) { + if ( + $property_type instanceof ReflectionNamedType + && $property_type->allowsNull() + && $property->getValue($target) === null + ) { return false; } - return $propertyTypeName - && (is_subclass_of($propertyTypeName, State::class) || $propertyTypeName === State::class || $propertyTypeName === StateCollection::class); + $all_property_types = match ($property_type::class) { + ReflectionUnionType::class, ReflectionIntersectionType::class => $property_type->getTypes(), + default => [$property_type], + }; + + foreach ($all_property_types as $type) { + $name = $type?->getName(); + if ($name && $this->isStateClass($name)) { + return true; + } + } + + return false; }) ->map(fn (ReflectionProperty $property) => $property->getValue($target)) ->flatten(); } + + protected function isStateClass(string $name): bool + { + return is_subclass_of($name, State::class) + || $name === State::class + || $name === StateCollection::class; + } } diff --git a/tests/Unit/UseStatesDirectlyInEventsTest.php b/tests/Unit/UseStatesDirectlyInEventsTest.php index 1f4e97be..b1c6a5ed 100644 --- a/tests/Unit/UseStatesDirectlyInEventsTest.php +++ b/tests/Unit/UseStatesDirectlyInEventsTest.php @@ -101,6 +101,24 @@ $this->assertEquals($event2->id, $user_request2->last_event_id); }); +it('supports union typed properties in events', function() { + $user_request = UserRequestState::new(); + + UserRequestsWithUnionTypes::commit( + user_request: $user_request, + value: 'foo' + ); + + $this->assertEquals($user_request->unionTypedValue, 'foo'); + + UserRequestsWithUnionTypes::commit( + user_request: $user_request, + value: 12 + ); + + $this->assertEquals($user_request->unionTypedValue, 12); +}); + class UserRequestState extends State { public bool $acknowledged = false; @@ -108,6 +126,8 @@ class UserRequestState extends State public bool $processed = false; public bool $nullable = false; + + public string|int $unionTypedValue = ''; } class UserRequestAcknowledged extends Event @@ -149,6 +169,18 @@ public function apply() } } +class UserRequestsWithUnionTypes extends Event +{ + public function __construct( + public UserRequestState $user_request, + public string|int $value + ) {} + + public function apply() { + $this->user_request->unionTypedValue = $this->value; + } +} + class ParentState extends State { public ChildState $child;