Skip to content

Commit e25711f

Browse files
committed
fix: throw correct exception from Activity and Workflow contexts
1 parent d00a397 commit e25711f

File tree

5 files changed

+271
-21
lines changed

5 files changed

+271
-21
lines changed

src/Activity.php

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@
1717
use Temporal\DataConverter\Type;
1818
use Temporal\DataConverter\ValuesInterface;
1919
use Temporal\Exception\OutOfContextException;
20-
use Temporal\Internal\Activity\ActivityContext;
2120
use Temporal\Internal\Support\Facade;
2221

2322
final class Activity extends Facade
@@ -29,10 +28,11 @@ final class Activity extends Facade
2928
public static function getCurrentContext(): ActivityContextInterface
3029
{
3130
$ctx = parent::getCurrentContext();
32-
/** @var ActivityContext $ctx */
33-
$ctx::class === ActivityContext::class or throw new OutOfContextException(
34-
'The Activity facade can only be used in the context of an activity execution.',
35-
);
31+
if (!$ctx instanceof ActivityContextInterface) {
32+
throw new OutOfContextException(
33+
'The Activity facade can only be used in the context of an activity execution.',
34+
);
35+
}
3636
return $ctx;
3737
}
3838

src/Internal/Support/Facade.php

Lines changed: 2 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -41,15 +41,8 @@ public static function setCurrentContext(?object $ctx): void
4141
self::$ctx = $ctx;
4242
}
4343

44-
/**
45-
* @throws OutOfContextException
46-
*/
47-
public static function getCurrentContext(): object
44+
public static function getCurrentContext(): ?object
4845
{
49-
if (self::$ctx === null) {
50-
throw new \RuntimeException(self::ERROR_NO_CONTEXT);
51-
}
52-
5346
return self::$ctx;
5447
}
5548

@@ -58,11 +51,7 @@ public static function getCurrentContext(): object
5851
*/
5952
public static function getContextId(): int
6053
{
61-
if (self::$ctx === null) {
62-
throw new \RuntimeException(self::ERROR_NO_CONTEXT);
63-
}
64-
65-
return \spl_object_id(self::$ctx);
54+
return \spl_object_id(static::getCurrentContext());
6655
}
6756

6857
/**

src/Workflow.php

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -59,9 +59,11 @@ final class Workflow extends Facade
5959
public static function getCurrentContext(): WorkflowContextInterface
6060
{
6161
$ctx = parent::getCurrentContext();
62-
$ctx instanceof WorkflowContextInterface or throw new OutOfContextException(
63-
'The Workflow facade can be used only inside workflow code.',
64-
);
62+
if (!$ctx instanceof WorkflowContextInterface) {
63+
throw new OutOfContextException(
64+
'The Workflow facade can be used only inside workflow code.',
65+
);
66+
}
6567
return $ctx;
6668
}
6769

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Temporal\Tests\Unit\Internal\Support;
6+
7+
use PHPUnit\Framework\Attributes\DataProvider;
8+
use PHPUnit\Framework\Attributes\Test;
9+
use PHPUnit\Framework\TestCase;
10+
use Temporal\Activity;
11+
use Temporal\Exception\OutOfContextException;
12+
13+
class ActivityFacadeTest extends TestCase
14+
{
15+
/**
16+
* @return iterable<string, array{callable}>
17+
*/
18+
public static function outOfContextMethods(): iterable
19+
{
20+
yield 'getCurrentContext' => [
21+
static fn() => Activity::getCurrentContext(),
22+
];
23+
24+
yield 'getInfo' => [
25+
static fn() => Activity::getInfo(),
26+
];
27+
28+
yield 'getInput' => [
29+
static fn() => Activity::getInput(),
30+
];
31+
32+
yield 'hasHeartbeatDetails' => [
33+
static fn() => Activity::hasHeartbeatDetails(),
34+
];
35+
36+
yield 'getHeartbeatDetails' => [
37+
static fn() => Activity::getHeartbeatDetails(),
38+
];
39+
40+
yield 'getCancellationDetails' => [
41+
static fn() => Activity::getCancellationDetails(),
42+
];
43+
44+
yield 'doNotCompleteOnReturn' => [
45+
static fn() => Activity::doNotCompleteOnReturn(),
46+
];
47+
48+
yield 'heartbeat' => [
49+
static fn() => Activity::heartbeat('test'),
50+
];
51+
52+
yield 'getInstance' => [
53+
static fn() => Activity::getInstance(),
54+
];
55+
}
56+
57+
#[Test]
58+
#[DataProvider('outOfContextMethods')]
59+
public function throwsOutOfContextException(callable $method): void
60+
{
61+
$this->expectException(OutOfContextException::class);
62+
$this->expectExceptionMessage('The Activity facade can only be used in the context of an activity execution.');
63+
64+
$method();
65+
}
66+
}
Lines changed: 193 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,193 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Temporal\Tests\Unit\Internal\Support;
6+
7+
use PHPUnit\Framework\Attributes\DataProvider;
8+
use PHPUnit\Framework\Attributes\Test;
9+
use PHPUnit\Framework\TestCase;
10+
use Temporal\Exception\OutOfContextException;
11+
use Temporal\Workflow;
12+
13+
class WorkflowFacadeTest extends TestCase
14+
{
15+
/**
16+
* @return iterable<string, array{callable, string}>
17+
*/
18+
public static function outOfContextMethods(): iterable
19+
{
20+
yield 'getCurrentContext' => [
21+
static fn() => Workflow::getCurrentContext(),
22+
];
23+
24+
yield 'now' => [
25+
static fn() => Workflow::now(),
26+
];
27+
28+
yield 'getInfo' => [
29+
static fn() => Workflow::getInfo(),
30+
];
31+
32+
yield 'getInput' => [
33+
static fn() => Workflow::getInput(),
34+
];
35+
36+
yield 'await' => [
37+
static fn() => Workflow::await(static fn() => true),
38+
];
39+
40+
yield 'awaitWithTimeout' => [
41+
static fn() => Workflow::awaitWithTimeout(1, static fn() => true),
42+
];
43+
44+
yield 'isReplaying' => [
45+
static fn() => Workflow::isReplaying(),
46+
];
47+
48+
yield 'getVersion' => [
49+
static fn() => Workflow::getVersion('test', Workflow::DEFAULT_VERSION, 1),
50+
];
51+
52+
yield 'timer' => [
53+
static fn() => Workflow::timer(1),
54+
];
55+
56+
yield 'executeChildWorkflow' => [
57+
static fn() => Workflow::executeChildWorkflow('Test'),
58+
];
59+
60+
yield 'newChildWorkflowStub' => [
61+
static fn() => Workflow::newChildWorkflowStub(\stdClass::class),
62+
];
63+
64+
yield 'newExternalWorkflowStub' => [
65+
static fn() => Workflow::newExternalWorkflowStub('test', new Workflow\WorkflowExecution()),
66+
];
67+
68+
yield 'continueAsNew' => [
69+
static fn() => Workflow::continueAsNew(''),
70+
];
71+
72+
yield 'async' => [
73+
static fn() => Workflow::async(static fn() => yield),
74+
];
75+
76+
yield 'asyncDetached' => [
77+
static fn() => Workflow::asyncDetached(static fn() => yield),
78+
];
79+
80+
yield 'newActivityStub' => [
81+
static fn() => Workflow::newActivityStub(\stdClass::class),
82+
];
83+
84+
yield 'newUntypedActivityStub' => [
85+
static fn() => Workflow::newUntypedActivityStub(),
86+
];
87+
88+
yield 'executeActivity' => [
89+
static fn() => Workflow::executeActivity('test'),
90+
];
91+
92+
yield 'getStackTrace' => [
93+
static fn() => Workflow::getStackTrace(),
94+
];
95+
96+
yield 'allHandlersFinished' => [
97+
static fn() => Workflow::allHandlersFinished(),
98+
];
99+
100+
yield 'upsertMemo' => [
101+
static fn() => Workflow::upsertMemo(['key' => 'value']),
102+
];
103+
104+
yield 'upsertSearchAttributes' => [
105+
static fn() => Workflow::upsertSearchAttributes(['key' => 'value']),
106+
];
107+
108+
yield 'uuid' => [
109+
static fn() => Workflow::uuid(),
110+
];
111+
112+
yield 'uuid4' => [
113+
static fn() => Workflow::uuid4(),
114+
];
115+
116+
yield 'uuid7' => [
117+
static fn() => Workflow::uuid7(),
118+
];
119+
120+
yield 'runLocked' => [
121+
static fn() => Workflow::runLocked(new \Temporal\Workflow\Mutex('test'), static fn() => yield),
122+
];
123+
124+
yield 'getLogger' => [
125+
static fn() => Workflow::getLogger(),
126+
];
127+
yield 'getInstance' => [
128+
static fn() => Workflow::getInstance(),
129+
];
130+
131+
yield 'getUpdateContext' => [
132+
static fn() => Workflow::getUpdateContext(),
133+
];
134+
135+
yield 'getLastCompletionResult' => [
136+
static fn() => Workflow::getLastCompletionResult(),
137+
];
138+
139+
yield 'registerQuery' => [
140+
static fn() => Workflow::registerQuery('test', static fn() => null),
141+
];
142+
143+
yield 'registerSignal' => [
144+
static fn() => Workflow::registerSignal('test', static fn() => null),
145+
];
146+
147+
yield 'registerDynamicSignal' => [
148+
static fn() => Workflow::registerDynamicSignal(static fn() => null),
149+
];
150+
151+
yield 'registerDynamicQuery' => [
152+
static fn() => Workflow::registerDynamicQuery(static fn() => null),
153+
];
154+
155+
yield 'registerDynamicUpdate' => [
156+
static fn() => Workflow::registerDynamicUpdate(static fn() => null),
157+
];
158+
159+
yield 'registerUpdate' => [
160+
static fn() => Workflow::registerUpdate('test', static fn() => null),
161+
];
162+
163+
yield 'sideEffect' => [
164+
static fn() => Workflow::sideEffect(static fn() => null),
165+
];
166+
167+
yield 'newContinueAsNewStub' => [
168+
static fn() => Workflow::newContinueAsNewStub(\stdClass::class),
169+
];
170+
171+
yield 'newUntypedChildWorkflowStub' => [
172+
static fn() => Workflow::newUntypedChildWorkflowStub('test'),
173+
];
174+
175+
yield 'newUntypedExternalWorkflowStub' => [
176+
static fn() => Workflow::newUntypedExternalWorkflowStub(new Workflow\WorkflowExecution()),
177+
];
178+
179+
yield 'upsertTypedSearchAttributes' => [
180+
static fn() => Workflow::upsertTypedSearchAttributes(),
181+
];
182+
}
183+
184+
#[Test]
185+
#[DataProvider('outOfContextMethods')]
186+
public function throwsOutOfContextException(callable $method): void
187+
{
188+
$this->expectException(OutOfContextException::class);
189+
$this->expectExceptionMessage('The Workflow facade can be used only inside workflow code.');
190+
191+
$method();
192+
}
193+
}

0 commit comments

Comments
 (0)