Skip to content

Commit c5b30ac

Browse files
committed
create type safety
Signed-off-by: Robert Landers <landers.robert@gmail.com>
1 parent a18fe4c commit c5b30ac

4 files changed

Lines changed: 52 additions & 34 deletions

File tree

src/DurableFuture.php

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,27 +25,34 @@
2525
namespace Bottledcode\DurablePhp;
2626

2727
use Amp\DeferredFuture;
28+
use Bottledcode\DurablePhp\State\Serializer;
29+
use LogicException;
2830

2931
/**
3032
* @template T
3133
*/
3234
class DurableFuture
3335
{
3436
/**
35-
* @param DeferredFuture<T> $future
37+
* @param DeferredFuture<T> $future
38+
* @param class-string<T>|null $resultType
3639
*/
37-
public function __construct(public readonly DeferredFuture $future) {}
40+
public function __construct(public readonly DeferredFuture $future, public readonly ?string $resultType = null) {}
3841

3942
/**
4043
* @return T
4144
*/
4245
public function getResult(): mixed
4346
{
4447
if ($this->future->isComplete()) {
45-
return $this->future->getFuture()->await();
48+
if ($this->resultType === null) {
49+
return $this->future->getFuture()->await();
50+
}
51+
52+
return Serializer::deserialize($this->future->getFuture()->await(), $this->resultType);
4653
}
4754

48-
throw new \LogicException('Future is not complete');
55+
throw new LogicException('Future is not complete');
4956
}
5057

5158
public function hasResult(): bool

src/OrchestrationContext.php

Lines changed: 21 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -70,19 +70,18 @@ final class OrchestrationContext implements OrchestrationContextInterface
7070

7171
private int $randomKey = 0;
7272

73-
public function callActivity(string $name, array $args = [], ?RetryOptions $retryOptions = null): DurableFuture
73+
public function callActivity(string $name, ?string $returnType = null, ?RetryOptions $retryOptions = null, mixed ...$args): DurableFuture
7474
{
7575
$this->durableLogger->debug('Calling activity', ['name' => $name]);
7676
$identity = $this->newGuid();
7777

7878
return $this->createFuture(
79-
fn()
80-
=> $this->taskController->fire(
81-
AwaitResult::forEvent(
82-
StateId::fromInstance($this->id),
83-
WithActivity::forEvent($identity, ScheduleTask::forName($name, $args)),
84-
),
79+
fn() => $this->taskController->fire(
80+
AwaitResult::forEvent(
81+
StateId::fromInstance($this->id),
82+
WithActivity::forEvent($identity, ScheduleTask::forName($name, $args)),
8583
),
84+
),
8685
function (Event $event, string $eventIdentity) use ($identity): array {
8786
if (($event instanceof TaskCompleted || $event instanceof TaskFailed) &&
8887
$eventIdentity === $identity->toString()) {
@@ -92,6 +91,7 @@ function (Event $event, string $eventIdentity) use ($identity): array {
9291
return [null, false];
9392
},
9493
$identity->toString(),
94+
$returnType,
9595
);
9696
}
9797

@@ -113,9 +113,10 @@ private function createFuture(
113113
Closure $onSent,
114114
Closure $onReceived,
115115
?string $identity = null,
116+
?string $resultType = null,
116117
): DurableFuture {
117118
$identity ??= $this->history->historicalTaskResults->getIdentity();
118-
if (!$this->history->historicalTaskResults->hasSentIdentity($identity)) {
119+
if (! $this->history->historicalTaskResults->hasSentIdentity($identity)) {
119120
$this->durableLogger->debug('Future requested for an unsent identity', [$identity]);
120121
[$eventId] = $onSent();
121122
$deferred = new DeferredFuture();
@@ -129,7 +130,7 @@ private function createFuture(
129130
$this->durableLogger->debug('Future requested for a sent identity, processing future', [$identity]);
130131

131132
$deferred = new DeferredFuture();
132-
$future = new DurableFuture($deferred);
133+
$future = new DurableFuture($deferred, $resultType);
133134
$this->history->historicalTaskResults->trackFuture($onReceived, $future);
134135

135136
return $future;
@@ -214,13 +215,12 @@ public function createTimer(DateTimeImmutable|DateInterval $fireAt): DurableFutu
214215
$identity = sha1($fireAt->format('c'));
215216

216217
return $this->createFuture(
217-
fn()
218-
=> $this->taskController->fire(
219-
WithOrchestration::forInstance(
220-
StateId::fromInstance($this->id),
221-
WithDelay::forEvent($fireAt, RaiseEvent::forTimer($identity)),
222-
),
218+
fn() => $this->taskController->fire(
219+
WithOrchestration::forInstance(
220+
StateId::fromInstance($this->id),
221+
WithDelay::forEvent($fireAt, RaiseEvent::forTimer($identity)),
223222
),
223+
),
224224
function (Event $event) use ($identity): array {
225225
if ($event instanceof RaiseEvent && $event->eventName === $identity) {
226226
return [$event, true];
@@ -236,10 +236,10 @@ public function getCurrentTime(): DateTimeImmutable
236236
return $this->history->historicalTaskResults->getCurrentTime();
237237
}
238238

239-
public function waitForExternalEvent(string $name): DurableFuture
239+
public function waitForExternalEvent(string $name, ?string $resultType = null): DurableFuture
240240
{
241241
$this->durableLogger->debug('Waiting for external event', ['name' => $name]);
242-
$future = new DurableFuture(new DeferredFuture());
242+
$future = new DurableFuture(new DeferredFuture(), $resultType);
243243
$this->history->historicalTaskResults->trackFuture(function (Event $event) use ($name): array {
244244
$found = false;
245245
$result = null;
@@ -370,7 +370,7 @@ public function isLockedOwned(EntityId $entityId): bool
370370
public function lockEntity(EntityId ...$entityId): EntityLock
371371
{
372372
$this->durableLogger->debug('Locking entities', ['entityId' => $entityId]);
373-
if (!empty($this->history->locks ?? []) && !$this->isReplaying()) {
373+
if (! empty($this->history->locks ?? []) && ! $this->isReplaying()) {
374374
throw new LogicException('Cannot lock an entity while holding locks');
375375
}
376376

@@ -447,7 +447,7 @@ public function waitAll(DurableFuture ...$tasks): array
447447
/**
448448
* @template T
449449
*
450-
* @param class-string<T> $className
450+
* @param class-string<T> $className
451451
* @return T
452452
*/
453453
public function createEntityProxy(string $className, ?EntityId $entityId = null): object
@@ -457,7 +457,7 @@ public function createEntityProxy(string $className, ?EntityId $entityId = null)
457457
}
458458

459459
$class = new ReflectionClass($className);
460-
if (!$class->isInterface()) {
460+
if (! $class->isInterface()) {
461461
throw new LogicException('Only interfaces can be proxied');
462462
}
463463

@@ -577,7 +577,7 @@ public function entityOp(string|EntityId $id, Closure $operation): mixed
577577
}
578578

579579
$name = $type->getName();
580-
if (!interface_exists($name)) {
580+
if (! interface_exists($name)) {
581581
throw new LogicException('Unable to load interface: ' . $name);
582582
}
583583

src/OrchestrationContextInterface.php

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -41,11 +41,12 @@ interface OrchestrationContextInterface
4141
* @template T
4242
*
4343
* @param string $name The name of the function to remotely invoke
44-
* @param array $args The arguments to pass to the function
44+
* @param class-string<T>|null $returnType
4545
* @param RetryOptions|null $retryOptions How to retry on failure
46+
* @param array $args The arguments to pass to the function
4647
* @return DurableFuture<T>
4748
*/
48-
public function callActivity(string $name, array $args = [], ?RetryOptions $retryOptions = null): DurableFuture;
49+
public function callActivity(string $name, ?string $returnType = null, ?RetryOptions $retryOptions = null, mixed ...$args): DurableFuture;
4950

5051
/**
5152
* Calls an activity inline. There are no retries and exceptions will cause an immediate failure.
@@ -94,9 +95,9 @@ public function lockEntity(EntityId ...$entityId): EntityLock;
9495

9596
public function callSubOrchestrator(
9697
string $name,
97-
array $args = [],
9898
?string $instanceId = null,
9999
?RetryOptions $retryOptions = null,
100+
mixed ...$args,
100101
): DurableFuture;
101102

102103
public function continueAsNew(array $args = []): never;
@@ -133,9 +134,10 @@ public function setCustomStatus(string $customStatus): void;
133134
*
134135
* @template T
135136
*
137+
* @param class-string<T>|null $resultType
136138
* @return DurableFuture<T>
137139
*/
138-
public function waitForExternalEvent(string $name): DurableFuture;
140+
public function waitForExternalEvent(string $name, ?string $resultType = null): DurableFuture;
139141

140142
/**
141143
* Gets the current time in a deterministic way. (always the time the execution started)
@@ -190,11 +192,20 @@ public function waitAny(DurableFuture ...$tasks): DurableFuture;
190192

191193
/**
192194
* Returns once all futures have completed.
195+
*
196+
* @template-covariant T
197+
*
198+
* @return array<DurableFuture<T>>
193199
*/
194200
public function waitAll(DurableFuture ...$tasks): array;
195201

196202
/**
197203
* Returns the result (or throws on failure) once a single future has completed.
204+
*
205+
* @template T
206+
*
207+
* @param DurableFuture<T> $task
208+
* @return T
198209
*/
199210
public function waitOne(DurableFuture $task): mixed;
200211

src/Testing/DummyOrchestrationContext.php

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -147,7 +147,7 @@ public function entityOp(EntityId|string $id, Closure $operation): mixed
147147
}
148148

149149
$name = $type->getName();
150-
if (!interface_exists($name)) {
150+
if (! interface_exists($name)) {
151151
throw new LogicException('Unable to load interface: ' . $name);
152152
}
153153

@@ -260,13 +260,13 @@ public function setCustomStatus(string $customStatus): void
260260
$this->status = $this->status->with(customStatus: $customStatus);
261261
}
262262

263-
public function waitForExternalEvent(string $name): DurableFuture
263+
public function waitForExternalEvent(string $name, ?string $resultType = null): DurableFuture
264264
{
265265
$future = new DeferredFuture();
266266
$value = $this->events[$name] ?? throw new LogicException('Event not found: ' . $name);
267267
$future->complete($value);
268268

269-
return new DurableFuture($future);
269+
return new DurableFuture($future, $resultType);
270270
}
271271

272272
public function getCurrentTime(): DateTimeImmutable
@@ -358,7 +358,7 @@ public function waitAll(DurableFuture ...$tasks): array
358358
{
359359
$results = [];
360360
foreach ($tasks as $task) {
361-
if (!$task->future->isComplete()) {
361+
if (! $task->future->isComplete()) {
362362
throw new LogicException('Not all futures are completed');
363363
}
364364
$results[] = $task->getResult();
@@ -376,7 +376,7 @@ public function createEntityProxy(
376376
}
377377

378378
$class = new ReflectionClass($className);
379-
if (!$class->isInterface()) {
379+
if (! $class->isInterface()) {
380380
throw new LogicException('Only interfaces can be proxied');
381381
}
382382

0 commit comments

Comments
 (0)