From b415f0f3e984201ea05ea139f61e5a72c1b776cb Mon Sep 17 00:00:00 2001 From: Robert Landers Date: Sun, 29 Oct 2023 22:32:01 +0100 Subject: [PATCH] allow using closures to signal things --- .idea/durable-php.iml | 1 - src/DurableClient.php | 10 ++++- src/EntityClient.php | 25 +++++++++--- src/EntityClientInterface.php | 9 ++++ src/Proxy/SpyProxy.php | 77 +++++++++++++++++++++++++++++++++++ 5 files changed, 113 insertions(+), 9 deletions(-) create mode 100644 src/Proxy/SpyProxy.php diff --git a/.idea/durable-php.iml b/.idea/durable-php.iml index 8ecbc753..ddcabc69 100644 --- a/.idea/durable-php.iml +++ b/.idea/durable-php.iml @@ -2,7 +2,6 @@ - diff --git a/src/DurableClient.php b/src/DurableClient.php index b2d73e6b..f5b9d37e 100644 --- a/src/DurableClient.php +++ b/src/DurableClient.php @@ -32,8 +32,9 @@ class DurableClient implements DurableClientInterface { - public function __construct(private EntityClientInterface $entityClient, private OrchestrationClientInterface $orchestrationClient) - { + public function __construct( + private EntityClientInterface $entityClient, private OrchestrationClientInterface $orchestrationClient + ) { } public function cleanEntityStorage(): void @@ -109,4 +110,9 @@ public function getEntitySnapshot(EntityId $entityId): EntityState|null { return $this->entityClient->getEntitySnapshot($entityId); } + + public function signal(EntityId|string $entityId, \Closure $signal): void + { + $this->entityClient->signal($entityId, $signal); + } } diff --git a/src/EntityClient.php b/src/EntityClient.php index c303c8a7..067ef851 100644 --- a/src/EntityClient.php +++ b/src/EntityClient.php @@ -30,6 +30,7 @@ use Bottledcode\DurablePhp\Events\RaiseEvent; use Bottledcode\DurablePhp\Events\WithDelay; use Bottledcode\DurablePhp\Events\WithEntity; +use Bottledcode\DurablePhp\Proxy\SpyProxy; use Bottledcode\DurablePhp\State\EntityHistory; use Bottledcode\DurablePhp\State\EntityId; use Bottledcode\DurablePhp\State\EntityState; @@ -40,7 +41,7 @@ class EntityClient implements EntityClientInterface use PartitionCalculator; - public function __construct(private Config $config, private Source $source) + public function __construct(private Config $config, private Source $source, private SpyProxy $spyProxy) { } @@ -54,6 +55,23 @@ public function listEntities(): \Generator throw new \Exception('Not implemented'); } + public function getEntitySnapshot(EntityId $entityId): EntityState|null + { + return $this->source->get(StateId::fromEntityId($entityId), EntityHistory::class)?->getState(); + } + + public function signal(EntityId|string $entityId, \Closure $signal): void + { + $interfaceReflector = new \ReflectionFunction($signal); + $interfaceName = $interfaceReflector->getParameters()[0]->getName(); + $spy = $this->spyProxy->define($interfaceName); + $class = new $spy($operationName, $arguments); + $signal($class); + $this->signalEntity( + is_string($entityId) ? new EntityId($interfaceName, $entityId) : $entityId, $operationName, $arguments + ); + } + public function signalEntity( EntityId $entityId, string $operationName, @@ -70,9 +88,4 @@ public function signalEntity( $this->source->storeEvent($event, false); } - - public function getEntitySnapshot(EntityId $entityId): EntityState|null - { - return $this->source->get(StateId::fromEntityId($entityId), EntityHistory::class)?->getState(); - } } diff --git a/src/EntityClientInterface.php b/src/EntityClientInterface.php index e06b757b..ceeef8af 100644 --- a/src/EntityClientInterface.php +++ b/src/EntityClientInterface.php @@ -59,6 +59,15 @@ public function signalEntity( \DateTimeImmutable $scheduledTime = null ): void; + /** + * Signals an entity using a closure + * + * @param EntityId|string $entityId The id of the entity to signal + * @param \Closure $signal + * @return void + */ + public function signal(EntityId|string $entityId, \Closure $signal): void; + /** * @template T * @param EntityId $entityId diff --git a/src/Proxy/SpyProxy.php b/src/Proxy/SpyProxy.php new file mode 100644 index 00000000..09832413 --- /dev/null +++ b/src/Proxy/SpyProxy.php @@ -0,0 +1,77 @@ +impureCall($method); + } + + protected function impureCall(\ReflectionMethod $method): string + { + $name = $method->getName(); + $params = $method->getParameters(); + $params = array_map( + function (\ReflectionParameter $param) { + $type = $param->getType(); + if ($type !== null) { + $type = $this->getTypes($type); + } + + return "$type \${$param->getName()}"; + }, + $params + ); + $params = implode(', ', $params); + $return = $method->getReturnType(); + $return = $return ? ": {$this->getTypes($return)}" : ''; + + return <<operation = __METHOD__; + \$this->arguments = func_get_args(); + throw new \Exception('Not implemented'); +} +EOT; + } + + protected function getName(\ReflectionClass $class): string + { + return "__SpyProxy_{$class->getShortName()}"; + } + + protected function impureSignal(\ReflectionMethod $method): string + { + return $this->impureCall($method); + } + + protected function preamble(\ReflectionClass $class): string + { + return <<<'EOT' +public function __construct(private string|null &$operation = null, private array|null &$arguments = null) {} +EOT; + } +}