Skip to content

Commit ded957f

Browse files
authored
Merge branch 'master' into env-config
2 parents 93d2bf4 + 7414627 commit ded957f

File tree

12 files changed

+260
-62
lines changed

12 files changed

+260
-62
lines changed

psalm-baseline.xml

Lines changed: 3 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -971,13 +971,6 @@
971971
<code><![CDATA[$reflection->getName()]]></code>
972972
</PropertyTypeCoercion>
973973
</file>
974-
<file src="src/Internal/Support/Facade.php">
975-
<InvalidDocblock>
976-
<code><![CDATA[object<T>|null]]></code>
977-
<code><![CDATA[private static ?object $ctx = null;]]></code>
978-
<code><![CDATA[public static function getCurrentContext(): object]]></code>
979-
</InvalidDocblock>
980-
</file>
981974
<file src="src/Internal/Support/Inheritance.php">
982975
<PossiblyFalseArgument>
983976
<code><![CDATA[$implements]]></code>
@@ -1487,16 +1480,9 @@
14871480
</UnsafeInstantiation>
14881481
</file>
14891482
<file src="src/Workflow.php">
1490-
<InvalidReturnStatement>
1491-
<code><![CDATA[self::getCurrentContext()->newActivityStub($class, $options)]]></code>
1492-
<code><![CDATA[self::getCurrentContext()->registerQuery($queryType, $handler, $description)]]></code>
1493-
<code><![CDATA[self::getCurrentContext()->registerSignal($name, $handler, $description)]]></code>
1494-
</InvalidReturnStatement>
1495-
<InvalidReturnType>
1496-
<code><![CDATA[ScopedContextInterface]]></code>
1497-
<code><![CDATA[ScopedContextInterface]]></code>
1498-
<code><![CDATA[T]]></code>
1499-
</InvalidReturnType>
1483+
<UndefinedInterfaceMethod>
1484+
<code><![CDATA[getUpdateContext]]></code>
1485+
</UndefinedInterfaceMethod>
15001486
</file>
15011487
<file src="src/Workflow/ChildWorkflowOptions.php">
15021488
<PossiblyNullReference>

src/Activity.php

Lines changed: 15 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -17,21 +17,32 @@
1717
use Temporal\DataConverter\Type;
1818
use Temporal\DataConverter\ValuesInterface;
1919
use Temporal\Exception\OutOfContextException;
20+
use Temporal\Internal\Activity\ActivityContext;
2021
use Temporal\Internal\Support\Facade;
2122

22-
/**
23-
* @template-extends Facade<ActivityContextInterface>
24-
*/
2523
final class Activity extends Facade
2624
{
25+
/**
26+
* Get the current Activity context.
27+
* @throws OutOfContextException
28+
*/
29+
public static function getCurrentContext(): ActivityContextInterface
30+
{
31+
$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+
);
36+
return $ctx;
37+
}
38+
2739
/**
2840
* Returns information about current activity execution.
2941
*
3042
* @throws OutOfContextException in the absence of the activity execution context.
3143
*/
3244
public static function getInfo(): ActivityInfo
3345
{
34-
/** @var ActivityContextInterface $context */
3546
$context = self::getCurrentContext();
3647

3748
return $context->getInfo();
@@ -57,7 +68,6 @@ public static function getInfo(): ActivityInfo
5768
*/
5869
public static function getInput(): ValuesInterface
5970
{
60-
/** @var ActivityContextInterface $context */
6171
$context = self::getCurrentContext();
6272

6373
return $context->getInput();
@@ -72,7 +82,6 @@ public static function getInput(): ValuesInterface
7282
*/
7383
public static function hasHeartbeatDetails(): bool
7484
{
75-
/** @var ActivityContextInterface $context */
7685
$context = self::getCurrentContext();
7786

7887
return $context->hasHeartbeatDetails();
@@ -88,7 +97,6 @@ public static function hasHeartbeatDetails(): bool
8897
*/
8998
public static function getHeartbeatDetails($type = null): mixed
9099
{
91-
/** @var ActivityContextInterface $context */
92100
$context = self::getCurrentContext();
93101

94102
return $context->getLastHeartbeatDetails($type);
@@ -101,7 +109,6 @@ public static function getHeartbeatDetails($type = null): mixed
101109
*/
102110
public static function getCancellationDetails(): ?ActivityCancellationDetails
103111
{
104-
/** @var ActivityContextInterface $context */
105112
$context = self::getCurrentContext();
106113

107114
return $context->getCancellationDetails();
@@ -118,7 +125,6 @@ public static function getCancellationDetails(): ?ActivityCancellationDetails
118125
*/
119126
public static function doNotCompleteOnReturn(): void
120127
{
121-
/** @var ActivityContextInterface $context */
122128
$context = self::getCurrentContext();
123129

124130
$context->doNotCompleteOnReturn();
@@ -150,7 +156,6 @@ public static function doNotCompleteOnReturn(): void
150156
*/
151157
public static function heartbeat($details): void
152158
{
153-
/** @var ActivityContextInterface $context */
154159
$context = self::getCurrentContext();
155160

156161
$context->heartbeat($details);
@@ -161,7 +166,6 @@ public static function heartbeat($details): void
161166
*/
162167
public static function getInstance(): object
163168
{
164-
/** @var ActivityContextInterface $context */
165169
$context = self::getCurrentContext();
166170

167171
return $context->getInstance();

src/Internal/Support/DateInterval.php

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,21 @@ public static function parse(mixed $interval, string $format = self::FORMAT_MILL
6060
{
6161
switch (true) {
6262
case \is_string($interval):
63-
return CarbonInterval::fromString($interval);
63+
$carbon = CarbonInterval::fromString($interval);
64+
if (self::isIso8601DurationFormat($interval)) {
65+
$builtin = new \DateInterval($interval);
66+
$carbon->compare($builtin) === 0 or \trigger_error(
67+
\sprintf(
68+
'Ambiguous duration "%s": Carbon and DateInterval parse it differently. ' .
69+
'Use new \DateInterval("%s") for ISO 8601 standard parsing or PT/P prefix to clarify intent.',
70+
$interval,
71+
$interval,
72+
),
73+
\E_USER_WARNING,
74+
);
75+
}
76+
77+
return $carbon;
6478

6579
case $interval instanceof \DateInterval:
6680
return CarbonInterval::instance($interval);
@@ -180,4 +194,27 @@ private static function validateFormat(string $format): void
180194
throw new \InvalidArgumentException($message);
181195
}
182196
}
197+
198+
/**
199+
* Checks if a string matches the ISO 8601 duration format that PHP's DateInterval constructor accepts.
200+
*
201+
* Valid format: P[n]Y[n]M[n]W[n]D[T[n]H[n]M[n]S]
202+
* - Must start with P (period)
203+
* - Date elements (Y, M, W, D) come before T
204+
* - Time elements (H, M, S) come after T
205+
* - At least one date or time element must be present
206+
* - Alternative datetime format P<date>T<time> is also supported
207+
*
208+
* Examples: P2D, PT5M, P1Y2M3DT4H5M6S, P0001-00-00T00:00:00
209+
*/
210+
private static function isIso8601DurationFormat(string $interval): bool
211+
{
212+
// ISO 8601 duration format: P[n]Y[n]M[n]W[n]D[T[n]H[n]M[n]S]
213+
// At least one element (Y, M, W, D, H, M, or S) must be present
214+
// Alternative format: P<date>T<time> like P0001-00-00T00:00:00
215+
return \preg_match(
216+
'/^P(?=.)(?:\d+Y)?(?:\d+M)?(?:\d+W)?(?:\d+D)?(?:T(?=.)(?:\d+H)?(?:\d+M)?(?:\d+(?:\.\d+)?S)?)?$|^P\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}$/',
217+
$interval,
218+
) === 1 && $interval !== 'P' && $interval !== 'PT';
219+
}
183220
}

src/Internal/Support/Facade.php

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,6 @@
1313

1414
use Temporal\Exception\OutOfContextException;
1515

16-
/**
17-
* @template T of object
18-
*/
1916
abstract class Facade
2017
{
2118
/**
@@ -26,9 +23,6 @@ abstract class Facade
2623
'from the currently running process'
2724
;
2825

29-
/**
30-
* @var object<T>|null
31-
*/
3226
private static ?object $ctx = null;
3327

3428
/**
@@ -40,7 +34,6 @@ private function __construct()
4034
}
4135

4236
/**
43-
* @param object<T>|null $ctx
4437
* @internal
4538
*/
4639
public static function setCurrentContext(?object $ctx): void
@@ -49,7 +42,6 @@ public static function setCurrentContext(?object $ctx): void
4942
}
5043

5144
/**
52-
* @return object<T>
5345
* @throws OutOfContextException
5446
*/
5547
public static function getCurrentContext(): object

src/Internal/Transport/Router/InvokeActivity.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,7 @@ static function (ActivityInput $input) use ($handler, $context): mixed {
101101
'handleActivityInbound',
102102
)(new ActivityInput($context->getInput(), $context->getHeader()));
103103

104+
/** @var ActivityContext $context */
104105
$context = Activity::getCurrentContext();
105106
if ($context->isDoNotCompleteOnReturn()) {
106107
$resolver->reject(DoNotCompleteOnResultException::create());

src/Internal/Transport/Router/StartWorkflow.php

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@
2525
use Temporal\Internal\Workflow\WorkflowContext;
2626
use Temporal\Worker\FeatureFlags;
2727
use Temporal\Worker\Transport\Command\ServerRequestInterface;
28-
use Temporal\Workflow;
2928
use Temporal\Workflow\WorkflowInfo;
3029

3130
final class StartWorkflow extends Route
@@ -90,7 +89,6 @@ public function handle(ServerRequestInterface $request, array $headers, Deferred
9089
);
9190
$runId = $request->getID();
9291

93-
Workflow::setCurrentContext($context);
9492
$process = new Process($this->services, $runId, $instance);
9593
$this->services->running->add($process);
9694
$resolver->resolve(EncodedValues::fromValues([null]));

src/Internal/Workflow/ActivityStub.php

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -70,9 +70,6 @@ public function execute(
7070

7171
protected function request(RequestInterface $request): PromiseInterface
7272
{
73-
/** @var Workflow\WorkflowContextInterface $context */
74-
$context = Workflow::getCurrentContext();
75-
76-
return $context->request($request);
73+
return Workflow::getCurrentContext()->request($request);
7774
}
7875
}

src/Internal/Workflow/ChildWorkflowStub.php

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -125,10 +125,7 @@ function (WorkflowExecution $execution) use ($name, $args) {
125125

126126
protected function request(RequestInterface $request, bool $cancellable = true): PromiseInterface
127127
{
128-
/** @var Workflow\WorkflowContextInterface $context */
129-
$context = Workflow::getCurrentContext();
130-
131-
return $context->request($request, cancellable: $cancellable);
128+
return Workflow::getCurrentContext()->request($request, cancellable: $cancellable);
132129
}
133130

134131
private function getOptionArray(): array

src/Internal/Workflow/ExternalWorkflowStub.php

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,6 @@ public function cancel(): PromiseInterface
7777
private function request(RequestInterface $request): PromiseInterface
7878
{
7979
// todo intercept
80-
/** @var Workflow\WorkflowContextInterface $context */
8180
$context = Workflow::getCurrentContext();
8281

8382
return $context->request($request);

src/Workflow.php

Lines changed: 28 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@
2323
use Temporal\Exception\Failure\CanceledFailure;
2424
use Temporal\Exception\OutOfContextException;
2525
use Temporal\Internal\Support\Facade;
26-
use Temporal\Internal\Workflow\ScopeContext;
2726
use Temporal\Workflow\ActivityStubInterface;
2827
use Temporal\Workflow\CancellationScopeInterface;
2928
use Temporal\Workflow\ChildWorkflowOptions;
@@ -45,18 +44,27 @@
4544
*
4645
* This is main class you can use in your workflow code.
4746
*
48-
* @method static ScopeContext getCurrentContext() Get current workflow context.
49-
*
5047
* @psalm-import-type TypeEnum from Type
5148
* @psalm-import-type DateIntervalValue from DateInterval
5249
* @see DateInterval
53-
*
54-
* @template-extends Facade<ScopedContextInterface>
5550
*/
5651
final class Workflow extends Facade
5752
{
5853
public const DEFAULT_VERSION = -1;
5954

55+
/**
56+
* Get the current Workflow context.
57+
* @throws OutOfContextException
58+
*/
59+
public static function getCurrentContext(): WorkflowContextInterface
60+
{
61+
$ctx = parent::getCurrentContext();
62+
$ctx instanceof WorkflowContextInterface or throw new OutOfContextException(
63+
'The Workflow facade can be used only inside workflow code.',
64+
);
65+
return $ctx;
66+
}
67+
6068
/**
6169
* Returns current datetime.
6270
*
@@ -192,7 +200,9 @@ public static function getInput(): ValuesInterface
192200
*/
193201
public static function async(callable $task): CancellationScopeInterface
194202
{
195-
return self::getCurrentContext()->async($task);
203+
$ctx = self::getCurrentContext();
204+
\assert($ctx instanceof ScopedContextInterface);
205+
return $ctx->async($task);
196206
}
197207

198208
/**
@@ -244,7 +254,9 @@ public static function async(callable $task): CancellationScopeInterface
244254
*/
245255
public static function asyncDetached(callable $task): CancellationScopeInterface
246256
{
247-
return self::getCurrentContext()->asyncDetached($task);
257+
$ctx = self::getCurrentContext();
258+
\assert($ctx instanceof ScopedContextInterface);
259+
return $ctx->asyncDetached($task);
248260
}
249261

250262
/**
@@ -352,7 +364,9 @@ public static function registerQuery(
352364
callable $handler,
353365
string $description = '',
354366
): ScopedContextInterface {
355-
return self::getCurrentContext()->registerQuery($queryType, $handler, $description);
367+
$ctx = self::getCurrentContext();
368+
\assert($ctx instanceof ScopedContextInterface);
369+
return $ctx->registerQuery($queryType, $handler, $description);
356370
}
357371

358372
/**
@@ -372,7 +386,9 @@ public static function registerQuery(
372386
*/
373387
public static function registerSignal(string $name, callable $handler, string $description = ''): ScopedContextInterface
374388
{
375-
return self::getCurrentContext()->registerSignal($name, $handler, $description);
389+
$ctx = self::getCurrentContext();
390+
\assert($ctx instanceof ScopedContextInterface);
391+
return $ctx->registerSignal($name, $handler, $description);
376392
}
377393

378394
/**
@@ -491,7 +507,9 @@ public static function registerUpdate(
491507
?callable $validator = null,
492508
string $description = '',
493509
): ScopedContextInterface {
494-
return self::getCurrentContext()->registerUpdate($name, $handler, $validator, $description);
510+
$ctx = self::getCurrentContext();
511+
\assert($ctx instanceof ScopedContextInterface);
512+
return $ctx->registerUpdate($name, $handler, $validator, $description);
495513
}
496514

497515
/**
@@ -995,7 +1013,6 @@ public static function getStackTrace(): string
9951013
*/
9961014
public static function allHandlersFinished(): bool
9971015
{
998-
/** @var ScopedContextInterface $context */
9991016
$context = self::getCurrentContext();
10001017

10011018
return $context->allHandlersFinished();
@@ -1081,7 +1098,6 @@ public static function upsertTypedSearchAttributes(SearchAttributeUpdate ...$upd
10811098
*/
10821099
public static function uuid(): PromiseInterface
10831100
{
1084-
/** @var ScopedContextInterface $context */
10851101
$context = self::getCurrentContext();
10861102

10871103
return $context->uuid();
@@ -1094,7 +1110,6 @@ public static function uuid(): PromiseInterface
10941110
*/
10951111
public static function uuid4(): PromiseInterface
10961112
{
1097-
/** @var ScopedContextInterface $context */
10981113
$context = self::getCurrentContext();
10991114

11001115
return $context->uuid4();
@@ -1111,7 +1126,6 @@ public static function uuid4(): PromiseInterface
11111126
*/
11121127
public static function uuid7(?\DateTimeInterface $dateTime = null): PromiseInterface
11131128
{
1114-
/** @var ScopedContextInterface $context */
11151129
$context = self::getCurrentContext();
11161130

11171131
return $context->uuid7($dateTime);

0 commit comments

Comments
 (0)