Skip to content

Commit dee2797

Browse files
Document PHP workflow authoring reference
1 parent 927219b commit dee2797

6 files changed

Lines changed: 374 additions & 0 deletions

File tree

Lines changed: 306 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,306 @@
1+
---
2+
sidebar_position: 6
3+
title: Workflow Authoring API Reference
4+
description: Reference for the stable v2 PHP workflow authoring facade, durable waits, metadata upserts, and message streams.
5+
tags:
6+
- reference
7+
- workflows
8+
- php
9+
- v2
10+
keywords:
11+
- Workflow::activity
12+
- Workflow::await
13+
- Workflow::inbox
14+
- MessageStream
15+
- PHP workflow authoring API
16+
---
17+
18+
# Workflow Authoring API Reference
19+
20+
The v2 PHP authoring API is the code surface that runs inside a workflow
21+
fiber. Application workflows extend `Workflow\V2\Workflow`, implement a
22+
`handle()` entry method, and use either the static `Workflow::...` facade or
23+
the equivalent `Workflow\V2\...` helper functions for durable commands.
24+
25+
This page is a signature reference. For narrative guidance, see
26+
[Workflows](./workflows.md), [Activities](./activities.md),
27+
[Signals](../features/signals.md), and [Message Streams](../features/message-streams.md).
28+
29+
## Base Workflow Class
30+
31+
```php
32+
use Workflow\V2\Workflow;
33+
34+
abstract class Workflow
35+
{
36+
public ?string $connection = null;
37+
public ?string $queue = null;
38+
39+
public function workflowId(): string;
40+
public function runId(): string;
41+
public function lastChild(): ?ChildWorkflowHandle;
42+
public function children(): array;
43+
public function historyLength(): int;
44+
public function historySize(): int;
45+
public function shouldContinueAsNew(): bool;
46+
}
47+
```
48+
49+
| Member | Use when | Return contract |
50+
| --- | --- | --- |
51+
| `workflowId()` | The workflow needs its stable public instance id. | Instance id string, unchanged across continue-as-new. |
52+
| `runId()` | The workflow needs the currently executing run id. | Run id string for the selected execution. |
53+
| `lastChild()` | The workflow needs to signal the most recently spawned child. | `ChildWorkflowHandle` or `null`. |
54+
| `children()` | The workflow needs handles for children visible to the current replay sequence. | List of `ChildWorkflowHandle`. |
55+
| `historyLength()` | The workflow needs a count-based history budget signal. | Current history event count. |
56+
| `historySize()` | The workflow needs a byte-based history budget signal. | Approximate persisted history size in bytes. |
57+
| `shouldContinueAsNew()` | The workflow should rotate before history becomes expensive. | `true` when configured history budgets recommend rotation. |
58+
59+
`workflowId()` is the public address for signals, updates, queries, and message
60+
streams. `runId()` is useful for diagnostics and selected-run tooling, but most
61+
authoring code should keep external callers on the instance id.
62+
63+
## Durable Commands
64+
65+
The static facade delegates to namespaced helpers in `Workflow\V2`. The two
66+
forms are equivalent:
67+
68+
```php
69+
use Workflow\V2\Workflow;
70+
use function Workflow\V2\activity;
71+
72+
$resultFromFacade = Workflow::activity(SendReceipt::class, $orderId);
73+
$resultFromHelper = activity(SendReceipt::class, $orderId);
74+
```
75+
76+
| Facade | Helper | Signature | Durable effect |
77+
| --- | --- | --- | --- |
78+
| `Workflow::activity()` | `activity()` | `activity(string $activity, mixed ...$arguments): mixed` | Schedules an activity and waits for its result. |
79+
| `Workflow::executeActivity()` | `activity()` | `executeActivity(string $activity, mixed ...$arguments): mixed` | Alias for `activity()`. |
80+
| `Workflow::child()` | `child()` | `child(string $workflow, mixed ...$arguments): mixed` | Starts a child workflow and waits for its result. |
81+
| `Workflow::executeChildWorkflow()` | `child()` | `executeChildWorkflow(string $workflow, mixed ...$arguments): mixed` | Alias for `child()`. |
82+
| `Workflow::async()` | `async()` | `async(callable $callback): mixed` | Runs a callable as an auto-generated child workflow. |
83+
| `Workflow::all()` | `all()` | `all(iterable $calls): mixed` | Waits for concurrent calls and returns results in iteration order. |
84+
| `Workflow::parallel()` | `all()` | `parallel(iterable $calls): mixed` | Alias for `all()`. |
85+
| `Workflow::await()` | `await()` | `await(callable|string $condition, int|string|CarbonInterval|null $timeout = null, ?string $conditionKey = null): mixed` | Waits for a named signal or replay-safe condition. |
86+
| `Workflow::awaitWithTimeout()` | `await()` | `awaitWithTimeout(int|string|CarbonInterval $timeout, callable|string $condition, ?string $conditionKey = null): mixed` | Waits for a signal or condition with an explicit timeout. |
87+
| `Workflow::awaitSignal()` | `await()` | `awaitSignal(string $name): mixed` | Waits for a named signal. |
88+
| `Workflow::timer()` | `timer()` | `timer(int|string|CarbonInterval $duration): mixed` | Suspends until durable time advances. |
89+
| `Workflow::sideEffect()` | `sideEffect()` | `sideEffect(callable $callback): mixed` | Records a non-deterministic result in history and replays it. |
90+
| `Workflow::uuid4()` | `uuid4()` | `uuid4(): mixed` | Generates a replay-stable UUIDv4. |
91+
| `Workflow::uuid7()` | `uuid7()` | `uuid7(): mixed` | Generates a replay-stable UUIDv7. |
92+
| `Workflow::continueAsNew()` | `continueAsNew()` | `continueAsNew(mixed ...$arguments): mixed` | Ends the current run and starts a new run for the same instance. |
93+
| `Workflow::getVersion()` | `getVersion()` | `getVersion(string $changeId, int $minSupported = WorkflowStub::DEFAULT_VERSION, int $maxSupported = 1): mixed` | Negotiates a replay-safe workflow-code version. |
94+
| `Workflow::patched()` | `patched()` | `patched(string $changeId): mixed` | Returns whether the run crossed a named patch marker. |
95+
| `Workflow::deprecatePatch()` | `deprecatePatch()` | `deprecatePatch(string $changeId): mixed` | Keeps a patch marker alive after old code is removed. |
96+
| `Workflow::upsertMemo()` | `upsertMemo()` | `upsertMemo(array $entries): void` | Updates non-indexed run metadata. |
97+
| `Workflow::upsertSearchAttributes()` | `upsertSearchAttributes()` | `upsertSearchAttributes(array $attributes): void` | Updates indexed operator-visible metadata. |
98+
| `Workflow::now()` | `now()` | `now(): CarbonInterface` | Reads deterministic workflow time. |
99+
100+
```php
101+
use Workflow\V2\Workflow;
102+
103+
final class FulfillmentWorkflow extends Workflow
104+
{
105+
public function handle(string $orderId): array
106+
{
107+
Workflow::upsertSearchAttributes([
108+
'order_id' => $orderId,
109+
'status' => 'packing',
110+
]);
111+
112+
$label = Workflow::activity(CreateShippingLabel::class, $orderId);
113+
Workflow::timer('15 minutes');
114+
$receipt = Workflow::activity(SendReceipt::class, $orderId, $label);
115+
116+
return [
117+
'receipt' => $receipt,
118+
'workflow_id' => $this->workflowId(),
119+
'run_id' => $this->runId(),
120+
];
121+
}
122+
}
123+
```
124+
125+
## Timer Helpers
126+
127+
Timer helpers are shorthand for `timer()` and `Workflow::timer()`:
128+
129+
```php
130+
use Workflow\V2\Workflow;
131+
132+
Workflow::seconds(30);
133+
Workflow::minutes(5);
134+
Workflow::hours(2);
135+
Workflow::days(1);
136+
Workflow::weeks(1);
137+
Workflow::months(1);
138+
Workflow::years(1);
139+
```
140+
141+
| Helper | Equivalent |
142+
| --- | --- |
143+
| `seconds(int $seconds)` | `timer($seconds)` |
144+
| `minutes(int $minutes)` | `timer($minutes * 60)` |
145+
| `hours(int $hours)` | `timer($hours * 3600)` |
146+
| `days(int $days)` | `timer($days * 86400)` |
147+
| `weeks(int $weeks)` | `timer($weeks * 604800)` |
148+
| `months(int $months)` | `timer("{$months} months")` |
149+
| `years(int $years)` | `timer("{$years} years")` |
150+
151+
Use explicit `timer()` calls when the duration comes from configuration or
152+
workflow input. Use timer helpers when the source code should read as a fixed
153+
business wait.
154+
155+
## Message Streams
156+
157+
Open durable message streams from the workflow instance:
158+
159+
```php
160+
use Workflow\V2\MessageStream;
161+
use Workflow\V2\Workflow;
162+
163+
final class AssistantWorkflow extends Workflow
164+
{
165+
public function handle(string $targetWorkflowId): array
166+
{
167+
$message = $this->inbox('ai.user')->receiveOne();
168+
169+
if ($message === null) {
170+
return ['status' => 'waiting'];
171+
}
172+
173+
$reply = $this->outbox('ai.assistant')->sendReference(
174+
targetInstanceId: $targetWorkflowId,
175+
payloadReference: 'app://payloads/reply-123',
176+
correlationId: $this->workflowId(),
177+
idempotencyKey: 'reply-123',
178+
metadata: ['kind' => 'assistant_reply'],
179+
);
180+
181+
return [
182+
'status' => 'sent',
183+
'stream' => $reply->stream_key,
184+
'sequence' => $reply->sequence,
185+
];
186+
}
187+
}
188+
```
189+
190+
| Method | Signature | Contract |
191+
| --- | --- | --- |
192+
| `$this->messages()` | `messages(?string $streamKey = null, ?MessageService $messages = null): MessageStream` | Opens the stream for reading or sending. |
193+
| `$this->inbox()` | `inbox(?string $streamKey = null, ?MessageService $messages = null): MessageStream` | Alias for inbound authoring code. |
194+
| `$this->outbox()` | `outbox(?string $streamKey = null, ?MessageService $messages = null): MessageStream` | Alias for outbound authoring code. |
195+
| `MessageStream::key()` | `key(): string` | Returns the stream key. |
196+
| `MessageStream::cursor()` | `cursor(): int` | Returns the durable cursor position for this run. |
197+
| `MessageStream::hasPending()` | `hasPending(): bool` | Returns whether unconsumed messages exist on the stream. |
198+
| `MessageStream::pendingCount()` | `pendingCount(): int` | Returns the number of unconsumed messages on the stream. |
199+
| `MessageStream::peek()` | `peek(int $limit = 100): Collection` | Reads pending messages without consuming them. |
200+
| `MessageStream::receive()` | `receive(int $limit = 1, ?int $consumedBySequence = null): Collection` | Reads and consumes messages, recording cursor advancement. |
201+
| `MessageStream::receiveOne()` | `receiveOne(?int $consumedBySequence = null): ?WorkflowMessage` | Reads and consumes one message. |
202+
| `MessageStream::sendReference()` | `sendReference(string $targetInstanceId, ?string $payloadReference = null, MessageChannel|string $channel = MessageChannel::WorkflowMessage, ?string $correlationId = null, ?string $idempotencyKey = null, array $metadata = [], ?DateTimeInterface $expiresAt = null): WorkflowMessage` | Sends an ordered payload-reference message to another workflow instance. |
203+
204+
Use message streams for repeated ordered messages with cursor semantics. Use
205+
[Signals](../features/signals.md) for one-shot external events and
206+
[Updates](../features/updates.md) for request/return mutations.
207+
208+
## Attributes And Contracts
209+
210+
```php
211+
use Workflow\QueryMethod;
212+
use Workflow\UpdateMethod;
213+
use Workflow\V2\Attributes\Signal;
214+
use Workflow\V2\Attributes\Type;
215+
use Workflow\V2\Workflow;
216+
217+
#[Type('order-approval')]
218+
#[Signal('approved-by', [
219+
['name' => 'approvedBy', 'type' => 'string', 'allows_null' => false],
220+
])]
221+
final class OrderApprovalWorkflow extends Workflow
222+
{
223+
private string $stage = 'waiting';
224+
225+
public function handle(): void
226+
{
227+
$this->stage = Workflow::awaitSignal('approved-by');
228+
}
229+
230+
#[QueryMethod('current-stage')]
231+
public function currentStage(): string
232+
{
233+
return $this->stage;
234+
}
235+
236+
#[UpdateMethod('mark-ready')]
237+
public function markReady(): string
238+
{
239+
return $this->stage = 'ready';
240+
}
241+
}
242+
```
243+
244+
| Attribute | Target | Stable contract |
245+
| --- | --- | --- |
246+
| `#[Type('type-key')]` | Workflow or activity class | Declares the language-neutral durable type key. |
247+
| `#[Signal('signal-name', [...])]` | Workflow class, repeatable | Declares accepted signal names and optional ordered parameter contracts. |
248+
| `#[QueryMethod('query-name')]` | Workflow method | Declares a replay-safe query name. Omit the name to use the PHP method name. |
249+
| `#[UpdateMethod('update-name')]` | Workflow method | Declares a replay-safe update name. Omit the name to use the PHP method name. |
250+
251+
Signals, queries, and updates are public workflow contracts. Prefer explicit
252+
names so PHP method renames do not become API breaks.
253+
254+
## Failure Surface
255+
256+
Authoring API failures are durable workflow failures unless the command is
257+
rejected before execution:
258+
259+
| Surface | Typical failure | Operator meaning |
260+
| --- | --- | --- |
261+
| `activity()` | Activity throws, times out, or exhausts retry policy. | The run records activity failure history and follows workflow error handling. |
262+
| `child()` | Child workflow fails, cancels, terminates, or times out. | The parent observes a child failure outcome at the waiting command. |
263+
| `await()` | Timeout elapses before the condition or signal is satisfied. | The wait returns or fails according to the selected await form. |
264+
| `timer()` | Invalid duration input after normalization. | Authoring code should pass positive durations or explicit zero-duration waits. |
265+
| `continueAsNew()` | New run cannot be created. | Current run remains the evidence point for the failed transition. |
266+
| `upsertSearchAttributes()` | Attribute key, count, or total size exceeds limits. | The run fails before invalid indexed metadata is persisted. |
267+
| `MessageStream::receive()` | No positive workflow history sequence is available. | Receive must occur from workflow execution, not direct service code. |
268+
| `MessageStream::sendReference()` | Payload reference, route, or storage contract is invalid downstream. | Message ordering remains separate from payload-store integrity. |
269+
270+
For payload and history limits, see [Structural Limits](../constraints/structural-limits.md).
271+
For command rejection responses outside PHP workflow code, see
272+
[Server API Reference](../polyglot/server-api-reference.md).
273+
274+
## Determinism Rules
275+
276+
Workflow code must be replay-safe. Keep irreversible or non-deterministic work
277+
behind durable commands:
278+
279+
```php
280+
use Workflow\V2\Workflow;
281+
282+
final class DeterministicWorkflow extends Workflow
283+
{
284+
public function handle(): array
285+
{
286+
$workflowTime = Workflow::now();
287+
$stableId = Workflow::uuid7();
288+
$remoteQuote = Workflow::activity(FetchQuote::class);
289+
290+
return [
291+
'time' => $workflowTime->toIso8601String(),
292+
'id' => $stableId,
293+
'quote' => $remoteQuote,
294+
];
295+
}
296+
}
297+
```
298+
299+
- Use `Workflow::now()` instead of wall-clock time in workflow branches.
300+
- Use `Workflow::uuid4()` or `Workflow::uuid7()` instead of direct randomness.
301+
- Put network calls, filesystem writes, email sends, and external side effects
302+
in activities.
303+
- Use `Workflow::sideEffect()` only when the value must be captured in history
304+
and the side effect itself is not the business action.
305+
- Use `Workflow::getVersion()`, `Workflow::patched()`, and
306+
`Workflow::deprecatePatch()` to evolve workflow code without breaking replay.

docs/search-and-navigation.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ common phrases to the smallest useful guide.
2727
| Search phrase | Start here | Then use |
2828
| --- | --- | --- |
2929
| `quickstart`, `install`, `first workflow` | [Getting Started](./getting-started.md) | [Sample App](./sample-app.md) |
30+
| `PHP`, `workflow authoring`, `API reference` | [Workflow Authoring API Reference](./defining-workflows/workflow-authoring-reference.md) | [Workflows](./defining-workflows/workflows.md), [Message Streams](./features/message-streams.md) |
3031
| `workflow stuck`, `failed activity`, `retry exhausted` | [Failures and Recovery](./failures-and-recovery.md) | [Monitoring](./monitoring.md) |
3132
| `Waterline`, `workflow dashboard`, `history export` | [Monitoring](./monitoring.md) | [AI-Assisted Development](./ai-assisted-development.md) |
3233
| `actionability`, `diagnostic_only`, `repair_state` | [Monitoring](./monitoring.md) | [Failures and Recovery](./failures-and-recovery.md) |

docs/topics.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ the sample app. The links below group those surfaces by job.
2626
- [Workflows](./defining-workflows/workflows.md) - define a workflow class.
2727
- [Activities](./defining-workflows/activities.md) - move non-deterministic work out of replay.
2828
- [Starting Workflows](./defining-workflows/starting-workflows.md) - start runs through the Laravel API.
29+
- [Workflow Authoring API Reference](./defining-workflows/workflow-authoring-reference.md) - look up exact v2 PHP facade signatures, message-stream methods, and replay rules.
2930
- [Message Streams](./features/message-streams.md) - receive repeated messages through `inbox()` and publish ordered replies through `outbox()`.
3031

3132
## Run And Operate

scripts/discoverability-contract.json

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
"defining-workflows/workflows.md",
88
"defining-workflows/activities.md",
99
"defining-workflows/starting-workflows.md",
10+
"defining-workflows/workflow-authoring-reference.md",
1011
"features/message-streams.md"
1112
]
1213
},
@@ -212,6 +213,12 @@
212213
"related": ["features/updates.md", "features/queries.md"],
213214
"aliases": ["Signals", "Updates", "Queries"]
214215
},
216+
{
217+
"query": "php workflow authoring api reference",
218+
"target": "defining-workflows/workflow-authoring-reference.md",
219+
"related": ["defining-workflows/workflows.md", "features/message-streams.md"],
220+
"aliases": ["Workflow::activity", "Workflow::await", "Workflow::inbox", "MessageStream"]
221+
},
215222
{
216223
"query": "timers wait sleep condition",
217224
"target": "features/timers.md",

0 commit comments

Comments
 (0)