Skip to content

Commit 20a0108

Browse files
v2: add static facade methods to Workflow authoring class (#171 phase 1)
Mirror the helpers under Workflow\V2\functions.php as static methods on the Workflow abstract base class so v2 authors can write: use Workflow\V2\Workflow; final class MyWorkflow extends Workflow { public function handle(): mixed { $result = Workflow::activity(SendEmail::class, $input); Workflow::timer('5 seconds'); Workflow::upsertMemo(['stage' => 'done']); return $result; } } Each static is a thin delegate to the equivalent namespaced helper (activity, child, async, all, parallel, await, awaitWithTimeout, awaitSignal, timer, sideEffect, continueAsNew, getVersion, upsertMemo, upsertSearchAttributes, seconds/minutes/hours/days/weeks/months/years, plus executeActivity and executeChildWorkflow aliases). The namespaced helpers remain in place as the functional-style alternative. The existing instance convenience `$this->child()` — which returns the most recently spawned ChildWorkflowHandle — conflicted with the new static `Workflow::child(...)` spawn API. Renamed the instance method to `$this->lastChild()` and updated the four V2 test fixtures that used it. No v1 back-compat shim is needed because v2 is unpublished. Also update the v2 scaffolding stub to show Workflow:: usage in a commented example so new workflows ship with the facade-idiomatic form. docs/api-stability.md now documents the authoring facade as part of the v2 public API surface alongside the Contracts\* interfaces and the server-facing Support\* stability list. Deferred from #171 to a follow-up: `Workflow::now()` (needs new deterministic workflow-time machinery), and the `startActivity` / `startChild` fire-and-forget variants (not yet represented in the runtime primitives). Refs #294 (blocker), #171. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 5e9d1c8 commit 20a0108

8 files changed

Lines changed: 517 additions & 6 deletions

docs/api-stability.md

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,34 @@ Additive changes — new public methods, new optional parameters with
7171
defaults, new constants — are minor-version changes and do not require a
7272
major bump.
7373

74+
## `Workflow\V2\Workflow` authoring facade
75+
76+
The abstract base class `Workflow\V2\Workflow` is the canonical authoring
77+
API for v2 workflows. It exposes two surfaces, both covered by the
78+
semver guarantee:
79+
80+
- **Instance members** applications rely on inside an `execute()` /
81+
`handle()` method: `workflowId()`, `runId()`, `lastChild()`,
82+
`children()`, `historyLength()`, `historySize()`, `shouldContinueAsNew()`,
83+
`addCompensation()`, `setParallelCompensation()`,
84+
`setContinueWithError()`, `compensate()`, and the public properties
85+
`$run`, `$connection`, `$queue`.
86+
- **Static method facade** mirroring the helpers in
87+
`Workflow\V2\functions.php`: `activity`, `executeActivity`, `child`,
88+
`executeChildWorkflow`, `async`, `all`, `parallel`, `await`,
89+
`awaitWithTimeout`, `awaitSignal`, `timer`, `sideEffect`,
90+
`continueAsNew`, `getVersion`, `upsertMemo`, `upsertSearchAttributes`,
91+
and the timer sugar `seconds`/`minutes`/`hours`/`days`/`weeks`/
92+
`months`/`years`.
93+
94+
The namespaced helper functions under `Workflow\V2\*` remain the
95+
equivalent functional-style surface and are equally stable. Choosing
96+
between the static facade and the namespaced helpers is a style
97+
preference; both produce identical `Support\*` Call value objects.
98+
99+
Adding new static methods to the facade is an additive (non-breaking)
100+
change. Removing or renaming a documented method is a major change.
101+
74102
## Pre-existing `Contracts\*` interfaces
75103

76104
Interfaces under `Workflow\V2\Contracts\*` are the preferred extension

src/Commands/stubs/workflow.v2.stub

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,13 @@ class {{ class }} extends Workflow
1212
{
1313
public function handle(): mixed
1414
{
15+
// Example:
16+
//
17+
// $result = Workflow::activity(MyActivity::class, $input);
18+
// Workflow::timer('5 seconds');
19+
// Workflow::upsertMemo(['stage' => 'done']);
20+
//
21+
// return $result;
1522
return null;
1623
}
1724
}

src/V2/Workflow.php

Lines changed: 255 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,41 @@
44

55
namespace Workflow\V2;
66

7+
use Carbon\CarbonInterval;
78
use Throwable;
89
use Workflow\Traits\ResolvesMethodDependencies;
910
use Workflow\V2\Models\WorkflowRun;
1011
use Workflow\V2\Support\ChildWorkflowHandles;
1112
use Workflow\V2\Support\HistoryBudget;
1213

14+
/**
15+
* Base class for v2 workflows.
16+
*
17+
* Application workflows extend this class and implement an `execute` (or
18+
* `handle`) entry method. The class also exposes the v2 authoring API as
19+
* a static facade so workflow code can read like:
20+
*
21+
* use Workflow\V2\Workflow;
22+
*
23+
* class MyWorkflow extends Workflow
24+
* {
25+
* public function execute(): mixed
26+
* {
27+
* $result = Workflow::activity(MyActivity::class, 'arg');
28+
* Workflow::timer('5 seconds');
29+
* Workflow::upsertMemo(['stage' => 'finalizing']);
30+
*
31+
* return $result;
32+
* }
33+
* }
34+
*
35+
* Each static method is a thin delegate to the equivalent namespaced
36+
* helper under `Workflow\V2\*` — authors can pick either style.
37+
*
38+
* @api Stable v2 authoring API. Adding new static methods is an additive
39+
* (non-breaking) change; removing or renaming a documented method
40+
* requires a major version bump. See docs/api-stability.md.
41+
*/
1342
abstract class Workflow
1443
{
1544
use ResolvesMethodDependencies;
@@ -44,7 +73,16 @@ public function runId(): string
4473
return $this->run->id;
4574
}
4675

47-
public function child(): ?ChildWorkflowHandle
76+
/**
77+
* The handle for the most recently spawned child workflow, or null if
78+
* this workflow has not spawned any children yet.
79+
*
80+
* Renamed from `child()` in v2 so the `Workflow::child(...)` static
81+
* facade can spawn child workflows. Use `lastChild()` for the handle
82+
* lookup and `Workflow::child(...)` / `Workflow::executeChildWorkflow(...)`
83+
* to spawn a new child.
84+
*/
85+
public function lastChild(): ?ChildWorkflowHandle
4886
{
4987
$children = $this->children();
5088

@@ -141,4 +179,220 @@ public function setCommandDispatchEnabled(bool $enabled): void
141179
{
142180
$this->commandDispatchEnabled = $enabled;
143181
}
182+
183+
// ── Static authoring facade ─────────────────────────────────────────────
184+
//
185+
// The static methods below delegate to the namespaced helpers under
186+
// `Workflow\V2\*` (see src/V2/functions.php). They exist so workflow
187+
// authors can use the more conventional `Workflow::timer(...)` form
188+
// without an extra import. See the class docblock for example usage.
189+
//
190+
// These are pure delegates. Behaviour, determinism guarantees, and
191+
// operand/return types come from the underlying helper functions.
192+
193+
/**
194+
* Invoke an activity and wait for its result.
195+
*
196+
* @see activity()
197+
*/
198+
public static function activity(string $activity, mixed ...$arguments): mixed
199+
{
200+
return activity($activity, ...$arguments);
201+
}
202+
203+
/**
204+
* Alias for {@see activity()} matching Temporal's `executeActivity` name.
205+
*/
206+
public static function executeActivity(string $activity, mixed ...$arguments): mixed
207+
{
208+
return activity($activity, ...$arguments);
209+
}
210+
211+
/**
212+
* Invoke a child workflow and wait for its result.
213+
*
214+
* @see child()
215+
*/
216+
public static function child(string $workflow, mixed ...$arguments): mixed
217+
{
218+
return child($workflow, ...$arguments);
219+
}
220+
221+
/**
222+
* Alias for {@see child()} matching Temporal's `executeChildWorkflow` name.
223+
*/
224+
public static function executeChildWorkflow(string $workflow, mixed ...$arguments): mixed
225+
{
226+
return child($workflow, ...$arguments);
227+
}
228+
229+
/**
230+
* Run a callable as an auto-generated child workflow.
231+
*
232+
* @see async()
233+
*/
234+
public static function async(callable $callback): mixed
235+
{
236+
return async($callback);
237+
}
238+
239+
/**
240+
* Await a list of concurrent calls and return their resolved results in
241+
* iteration order.
242+
*
243+
* @see all()
244+
*/
245+
public static function all(iterable $calls): mixed
246+
{
247+
return all($calls);
248+
}
249+
250+
/**
251+
* Alias for {@see all()} matching the "run these in parallel" intent.
252+
*/
253+
public static function parallel(iterable $calls): mixed
254+
{
255+
return all($calls);
256+
}
257+
258+
/**
259+
* Wait for a condition closure to become truthy, for a signal by name,
260+
* or for either plus a timeout.
261+
*
262+
* @see await()
263+
*/
264+
public static function await(
265+
callable|string $condition,
266+
int|string|CarbonInterval|null $timeout = null,
267+
?string $conditionKey = null,
268+
): mixed {
269+
return await($condition, $timeout, $conditionKey);
270+
}
271+
272+
/**
273+
* Wait for a condition or signal with an explicit timeout, failing the
274+
* await when the timeout elapses.
275+
*/
276+
public static function awaitWithTimeout(
277+
int|string|CarbonInterval $timeout,
278+
callable|string $condition,
279+
?string $conditionKey = null,
280+
): mixed {
281+
return await($condition, $timeout, $conditionKey);
282+
}
283+
284+
/**
285+
* Wait for a named signal. Equivalent to `await(<signal name>)`.
286+
*/
287+
public static function awaitSignal(string $name): mixed
288+
{
289+
return await($name);
290+
}
291+
292+
/**
293+
* Suspend until a timer fires.
294+
*
295+
* @see timer()
296+
*/
297+
public static function timer(int|string|CarbonInterval $duration): mixed
298+
{
299+
return timer($duration);
300+
}
301+
302+
/**
303+
* Capture the result of a side-effect closure in history so replay
304+
* returns the same value on subsequent attempts.
305+
*
306+
* @see sideEffect()
307+
*/
308+
public static function sideEffect(callable $callback): mixed
309+
{
310+
return sideEffect($callback);
311+
}
312+
313+
/**
314+
* Terminate the current run by starting a new one with the provided
315+
* arguments, preserving workflow instance identity.
316+
*
317+
* @see continueAsNew()
318+
*/
319+
public static function continueAsNew(mixed ...$arguments): mixed
320+
{
321+
return continueAsNew(...$arguments);
322+
}
323+
324+
/**
325+
* Declare a workflow-code versioning change and receive the negotiated
326+
* version for the current run.
327+
*
328+
* @see getVersion()
329+
*/
330+
public static function getVersion(
331+
string $changeId,
332+
int $minSupported = WorkflowStub::DEFAULT_VERSION,
333+
int $maxSupported = 1,
334+
): mixed {
335+
return getVersion($changeId, $minSupported, $maxSupported);
336+
}
337+
338+
/**
339+
* Upsert non-indexed memo metadata on the current workflow run.
340+
*
341+
* @param array<string, mixed> $entries
342+
*
343+
* @see upsertMemo()
344+
*/
345+
public static function upsertMemo(array $entries): void
346+
{
347+
upsertMemo($entries);
348+
}
349+
350+
/**
351+
* Upsert indexed search attributes on the current workflow run.
352+
*
353+
* @param array<string, scalar|null> $attributes
354+
*
355+
* @see upsertSearchAttributes()
356+
*/
357+
public static function upsertSearchAttributes(array $attributes): void
358+
{
359+
upsertSearchAttributes($attributes);
360+
}
361+
362+
// Timer sugar --------------------------------------------------------
363+
364+
public static function seconds(int $seconds): mixed
365+
{
366+
return seconds($seconds);
367+
}
368+
369+
public static function minutes(int $minutes): mixed
370+
{
371+
return minutes($minutes);
372+
}
373+
374+
public static function hours(int $hours): mixed
375+
{
376+
return hours($hours);
377+
}
378+
379+
public static function days(int $days): mixed
380+
{
381+
return days($days);
382+
}
383+
384+
public static function weeks(int $weeks): mixed
385+
{
386+
return weeks($weeks);
387+
}
388+
389+
public static function months(int $months): mixed
390+
{
391+
return months($months);
392+
}
393+
394+
public static function years(int $years): mixed
395+
{
396+
return years($years);
397+
}
144398
}

tests/Fixtures/V2/TestChildHandleParentWorkflow.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ public function handle(): array
2727
#[QueryMethod('current-child-handle')]
2828
public function currentChildHandle(): ?array
2929
{
30-
$handle = $this->child();
30+
$handle = $this->lastChild();
3131

3232
if ($handle === null) {
3333
return null;
@@ -43,6 +43,6 @@ public function currentChildHandle(): ?array
4343
#[UpdateMethod('approve-child')]
4444
public function approveChild(string $approvedBy): void
4545
{
46-
$this->child()?->signal('approved-by', $approvedBy);
46+
$this->lastChild()?->signal('approved-by', $approvedBy);
4747
}
4848
}

tests/Fixtures/V2/TestParallelChildHandlesWorkflow.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ public function childHandles(): array
3838
#[QueryMethod('latest-child-handle')]
3939
public function latestChildHandle(): ?array
4040
{
41-
$handle = $this->child();
41+
$handle = $this->lastChild();
4242

4343
if ($handle === null) {
4444
return null;

tests/Fixtures/V2/TestParentWaitingOnContinuingChildWorkflow.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ public function handle(int $count = 0, int $max = 1): array
2626
#[QueryMethod('current-child-handle')]
2727
public function currentChildHandle(): ?array
2828
{
29-
$handle = $this->child();
29+
$handle = $this->lastChild();
3030

3131
if ($handle === null) {
3232
return null;

tests/Fixtures/V2/TestQueryChildResolutionAuthorityWorkflow.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ public function handle(int $seconds): array
3131
#[QueryMethod('current-state')]
3232
public function currentState(): array
3333
{
34-
$handle = $this->child();
34+
$handle = $this->lastChild();
3535

3636
return [
3737
'stage' => $this->stage,

0 commit comments

Comments
 (0)