Skip to content

Commit 51da0a7

Browse files
add methods to entity context (#158)
<!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Added a method to signal an entity using a callable, enabling more flexible signaling. * Introduced the ability to retrieve a snapshot of an entity's current state. * **Documentation** * Improved PHPDoc comments and type annotations for better clarity and type safety. * **Style** * Made minor formatting and style improvements for consistency and readability. <!-- end of auto-generated comment: release notes by coderabbit.ai --> Signed-off-by: Robert Landers <landers.robert@gmail.com>
1 parent c3b3e7d commit 51da0a7

4 files changed

Lines changed: 86 additions & 26 deletions

File tree

src/EntityContext.php

Lines changed: 50 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -41,9 +41,11 @@
4141
use Bottledcode\DurablePhp\Events\WithOrchestration;
4242
use Bottledcode\DurablePhp\Exceptions\Unwind;
4343
use Bottledcode\DurablePhp\Glue\Provenance;
44+
use Bottledcode\DurablePhp\Proxy\SpyException;
4445
use Bottledcode\DurablePhp\Proxy\SpyProxy;
4546
use Bottledcode\DurablePhp\State\EntityHistory;
4647
use Bottledcode\DurablePhp\State\EntityId;
48+
use Bottledcode\DurablePhp\State\EntityState;
4749
use Bottledcode\DurablePhp\State\Ids\StateId;
4850
use Closure;
4951
use Crell\Serde\Attributes\ClassSettings;
@@ -111,29 +113,6 @@ public function setState(mixed $value): void
111113
$this->state = $value;
112114
}
113115

114-
public function signalEntity(
115-
EntityId $entityId,
116-
string $operation,
117-
array $input = [],
118-
?DateTimeImmutable $scheduledTime = null,
119-
): void {
120-
$event = $this->addFrom(
121-
WithEntity::forInstance(
122-
StateId::fromEntityId($entityId),
123-
RaiseEvent::forOperation($operation, $input),
124-
),
125-
);
126-
if ($scheduledTime) {
127-
$event = WithDelay::forEvent($scheduledTime, $event);
128-
}
129-
$this->eventDispatcher->fire($event);
130-
}
131-
132-
private function addFrom(Event $event): Event
133-
{
134-
return WithFrom::forEvent($this->from, $event);
135-
}
136-
137116
public function getId(): EntityId
138117
{
139118
return $this->id;
@@ -161,6 +140,11 @@ public function startNewOrchestration(string $orchestration, array $input = [],
161140
);
162141
}
163142

143+
private function addFrom(Event $event): Event
144+
{
145+
return WithFrom::forEvent($this->from, $event);
146+
}
147+
164148
public function delay(Closure $self, DateTimeInterface $until = new DateTimeImmutable()): void
165149
{
166150
$classReflector = new ReflectionClass($this->history->getState());
@@ -283,4 +267,47 @@ public function revokeRole(string $role): void
283267
),
284268
);
285269
}
270+
271+
public function signal(EntityId $entityId, callable $signal): void
272+
{
273+
$proxy = $this->spyProxy->define($entityId->name);
274+
$operationName = null;
275+
$arguments = null;
276+
$proxy = new $proxy($operationName, $arguments);
277+
try {
278+
$proxy($signal);
279+
280+
if ($operationName === null || $arguments === null) {
281+
return;
282+
}
283+
} catch (SpyException) {
284+
$this->signalEntity($entityId, $operationName, $arguments ?? []);
285+
}
286+
}
287+
288+
public function signalEntity(
289+
EntityId $entityId,
290+
string $operation,
291+
array $input = [],
292+
?DateTimeImmutable $scheduledTime = null,
293+
): void {
294+
$event = $this->addFrom(
295+
WithEntity::forInstance(
296+
StateId::fromEntityId($entityId),
297+
RaiseEvent::forOperation($operation, $input),
298+
),
299+
);
300+
if ($scheduledTime) {
301+
$event = WithDelay::forEvent($scheduledTime, $event);
302+
}
303+
$this->eventDispatcher->fire($event);
304+
}
305+
306+
public function getSnapshot(EntityId $entityId): EntityState
307+
{
308+
$state = $this->eventDispatcher->getState(StateId::fromEntityId($entityId));
309+
assert($state instanceof EntityHistory);
310+
311+
return $state->getState();
312+
}
286313
}

src/EntityContextInterface.php

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626

2727
use Bottledcode\DurablePhp\Events\Shares\Operation;
2828
use Bottledcode\DurablePhp\State\EntityId;
29+
use Bottledcode\DurablePhp\State\EntityState;
2930
use Closure;
3031
use Crell\Serde\Attributes\ClassNameTypeMap;
3132
use DateTimeImmutable;
@@ -102,6 +103,26 @@ public function getId(): EntityId;
102103
*/
103104
public function getOperation(): string;
104105

106+
/**
107+
* Call the entity with a single signal
108+
*
109+
* @template T of EntityState
110+
*
111+
* @param EntityId<T> $entityId
112+
* @param callable(T): void $signal
113+
*/
114+
public function signal(EntityId $entityId, callable $signal): void;
115+
116+
/**
117+
* Retrieve a snapshot of the remote entity state
118+
*
119+
* @template T of EntityState
120+
*
121+
* @param EntityId<T> $entityId
122+
* @return EntityState<T>
123+
*/
124+
public function getSnapshot(EntityId $entityId): EntityState;
125+
105126
public function startNewOrchestration(string $orchestration, array $input = [], ?string $id = null): void;
106127

107128
public function delayUntil(

src/Proxy/Generator.php

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,13 @@
3434

3535
abstract class Generator
3636
{
37-
public function __construct(protected string|null $cacheDir = null) {}
37+
public function __construct(protected ?string $cacheDir = null) {}
3838

39+
/**
40+
* Ensures that the given interface is implemented
41+
*
42+
* @throws \ReflectionException
43+
*/
3944
public function define(string $interface): string
4045
{
4146
$name = $this->getName($class = new ReflectionClass($interface));
@@ -45,14 +50,15 @@ public function define(string $interface): string
4550
$cacheFile = $this->cacheDir . DIRECTORY_SEPARATOR . $name . '.php';
4651
if (file_exists($cacheFile)) {
4752
require_once $cacheFile;
53+
4854
return '\\' . $namespace . '\\' . $name;
4955
}
5056
}
5157

5258
$reflection = new ReflectionClass($interface);
5359
$fullname = $this->getInterfaceNamespace($reflection) . '\\' . $this->getName($reflection);
5460

55-
if (!class_exists($fullname)) {
61+
if (! class_exists($fullname)) {
5662
eval($output = $this->generate($interface));
5763
if ($cacheFile) {
5864
file_put_contents($cacheFile, "<?php\n{$output}");
@@ -106,6 +112,7 @@ function (ReflectionMethod $method) {
106112
$hooks[] = $this->pureMethod($hook, true);
107113
}
108114
$hooks[] = '}';
115+
109116
return implode(
110117
"\n",
111118
array_filter(
@@ -115,6 +122,7 @@ function (ReflectionMethod $method) {
115122
);
116123
}, $props);
117124
$props = implode("\n", $props);
125+
118126
return <<<EOT
119127
{$namespace}
120128
@@ -139,6 +147,7 @@ protected function getTypes(ReflectionIntersectionType|ReflectionNamedType|Refle
139147
if ($type->isBuiltin()) {
140148
return $nullable . $type->getName();
141149
}
150+
142151
return $nullable . '\\' . $type->getName();
143152
}
144153

src/State/EntityId.php

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,10 +28,13 @@
2828
use Withinboredom\Record;
2929

3030
/**
31-
* @template T
31+
* @template T of EntityState
3232
*/
3333
readonly class EntityId extends Record implements Stringable
3434
{
35+
/**
36+
* @var class-string<T>
37+
*/
3538
public protected(set) string $name;
3639

3740
public protected(set) string $id;

0 commit comments

Comments
 (0)