Workflow message passing - PHP SDK
Topics covered in this section
How to develop with Signals
A Signal is a message sent to a running Workflow Execution.
Signals are defined in your code and handled in your Workflow Definition. Signals can be sent to Workflow Executions from a Temporal Client or from another Workflow Execution.
How to define a Signal
A Signal has a name and can have arguments.
- The name, also called a Signal type, is a string.
- The arguments must be serializable.
Workflows can answer synchronous Queries and receive Signals.
All interface methods must have one of the following attributes:
- #[WorkflowMethod] indicates an entry point to a Workflow.
It contains parameters that specify timeouts and a Task Queue name.
Required parameters (such as
executionStartToCloseTimeoutSeconds
) that are not specified through the attribute must be provided at runtime. - #[SignalMethod] indicates a method that reacts to external signals. It must have a
void
return type. - #[QueryMethod] indicates a method that reacts to synchronous query requests. It must have a non
void
return type.
It is possible (though not recommended for usability reasons) to annotate concrete class implementation.
You can have more than one method with the same attribute (except #[WorkflowMethod]).
For example:
use Temporal\Workflow\WorkflowInterface;
use Temporal\Workflow\WorkflowMethod;
use Temporal\Workflow\SignalMethod;
use Temporal\Workflow\QueryMethod;
#[WorkflowInterface]
interface FileProcessingWorkflow
{
#[WorkflowMethod]
public function processFile(Argument $args);
#[QueryMethod("history")]
public function getHistory(): array;
#[QueryMethod("status")]
public function getStatus(): string;
#[SignalMethod]
public function retryNow(): void;
#[SignalMethod]
public function abandon(): void;
}
Note that name parameter of Workflow method attributes can be used to specify name of Workflow, Signal and Query types. If name is not specified the short name of the Workflow interface is used.
In the above code the #[WorkflowMethod(name)]
is not specified, thus the Workflow Type defaults to "FileProcessingWorkflow"
.
How to handle a Signal
Workflows listen for Signals by the Signal's name.
Use the #[SignalMethod]
attribute to handle Signals in the Workflow interface:
use Temporal\Workflow;
#[Workflow\WorkflowInterface]
class YourWorkflow
{
private bool $value;
#[Workflow\WorkflowMethod]
public function run()
{
yield Workflow::await(fn()=> $this->value);
return 'OK';
}
#[Workflow\SignalMethod]
public function setValue(bool $value)
{
$this->value = $value;
}
}
In the preceding example, the Workflow updates the protected value.
The main Workflow coroutine waits for the value to change by using the Workflow::await()
function.
How to send a Signal from a Temporal Client
When a Signal is sent successfully from the Temporal Client, the WorkflowExecutionSignaled Event appears in the Event History of the Workflow that receives the Signal.
To send a Signal to a Workflow Execution from a Client, call the Signal method, annotated with #[SignalMethod]
in the Workflow interface, from the Client code.
To send a Signal to a Workflow, use WorkflowClient->newWorkflowStub
or WorkflowClient->newUntypedWorkflowStub
:
$workflow = $workflowClient->newWorkflowStub(YourWorkflow::class);
$run = $workflowClient->start($workflow);
// do something
$workflow->setValue(true);
assert($run->getValue() === true);
Use WorkflowClient->newRunningWorkflowStub
or WorkflowClient->newUntypedRunningWorkflowStub
with Workflow Id to send Signals to already running Workflows.
$workflow = $workflowClient->newRunningWorkflowStub(YourWorkflow::class, 'workflowID');
$workflow->setValue(true);
See Handle Signal for details on how to handle Signals in a Workflow.
How to send a Signal from a Workflow
A Workflow can send a Signal to another Workflow, in which case it's called an External Signal.
When an External Signal is sent:
- A SignalExternalWorkflowExecutionInitiated Event appears in the sender's Event History.
- A WorkflowExecutionSignaled Event appears in the recipient's Event History.
To send Signal to a Workflow use WorkflowClient
->newWorkflowStub
or WorkflowClient
->newUntypedWorkflowStub
:
$workflow = $workflowClient->newWorkflowStub(YourWorkflow::class);
$run = $workflowClient->start($workflow);
// do something
$workflow->setValue(true);
assert($run->getValue() === true);
Use WorkflowClient
->newRunningWorkflowStub
or WorkflowClient->newUntypedRunningWorkflowStub
with Workflow Id to send
Signals to a running Workflow.
$workflow = $workflowClient->newRunningWorkflowStub(YourWorkflow::class, 'workflowID');
$workflow->setValue(true);
How to Signal-With-Start
Signal-With-Start is used from the Client. It takes a Workflow Id, Workflow arguments, a Signal name, and Signal arguments.
If there's a Workflow running with the given Workflow Id, it will be signaled. If there isn't, a new Workflow will be started and immediately signaled.
In cases where you may not know if a Workflow is running, and want to send a Signal to it, use startwithSignal
.
If a running Workflow exists, the startwithSignal
API sends the Signal.
If there is no running Workflow, the API starts a new Workflow Run and delivers the Signal to it.
$workflow = $workflowClient->newWorkflowStub(YourWorkflow::class);
$run = $workflowClient->startWithSignal(
$workflow,
'setValue',
[true], // signal arguments
[] // start arguments
);
How to develop with Queries
A Query is a synchronous operation that is used to get the state of a Workflow Execution.
How to define a Query
A Query has a name and can have arguments.
- The name, also called a Query type, is a string.
- The arguments must be serializable.
Workflows can answer synchronous Queries and receive Signals.
All interface methods must have one of the following attributes:
- #[WorkflowMethod] indicates an entry point to a Workflow.
It contains parameters that specify timeouts and a Task Queue name.
Required parameters (such as
executionStartToCloseTimeoutSeconds
) that are not specified through the attribute must be provided at runtime. - #[SignalMethod] indicates a method that reacts to external signals. It must have a
void
return type. - #[QueryMethod] indicates a method that reacts to synchronous query requests. It must have a non
void
return type.
It is possible (though not recommended for usability reasons) to annotate concrete class implementation.
You can have more than one method with the same attribute (except #[WorkflowMethod]).
For example:
use Temporal\Workflow\WorkflowInterface;
use Temporal\Workflow\WorkflowMethod;
use Temporal\Workflow\SignalMethod;
use Temporal\Workflow\QueryMethod;
#[WorkflowInterface]
interface FileProcessingWorkflow
{
#[WorkflowMethod]
public function processFile(Argument $args);
#[QueryMethod("history")]
public function getHistory(): array;
#[QueryMethod("status")]
public function getStatus(): string;
#[SignalMethod]
public function retryNow(): void;
#[SignalMethod]
public function abandon(): void;
}
Note that name parameter of Workflow method attributes can be used to specify name of Workflow, Signal and Query types. If name is not specified the short name of the Workflow interface is used.
In the above code the #[WorkflowMethod(name)]
is not specified, thus the Workflow Type defaults to "FileProcessingWorkflow"
.
How to handle a Query
Queries are handled by your Workflow.
Don't include any logic that causes Command generation within a Query handler (such as executing Activities). Including such logic causes unexpected behavior.
You can add custom Query types to handle Queries such as Querying the current state of a
Workflow, or Querying how many Activities the Workflow has completed. To do this, you need to set
up a Query handler using method attribute QueryMethod
or Workflow::registerQuery
.
#[Workflow\WorkflowInterface]
class YourWorkflow
{
#[Workflow\QueryMethod]
public function getValue()
{
return 42;
}
#[Workflow\WorkflowMethod]
public function run()
{
// workflow code
}
}
The handler function can receive any number of input parameters, but all input parameters must be
serializable. The following sample code sets up a Query handler that handles the Query type of
currentState
:
#[Workflow\WorkflowInterface]
class YourWorkflow
{
private string $currentState;
#[Workflow\QueryMethod('current_state')]
public function getCurrentState(): string
{
return $this->currentState;
}
#[Workflow\WorkflowMethod]
public function run()
{
// Your normal Workflow code begins here, and you update the currentState
// as the code makes progress.
$this->currentState = 'waiting timer';
try{
yield Workflow::timer(DateInterval::createFromDateString('1 hour'));
} catch (\Throwable $e) {
$this->currentState = 'timer failed';
throw $e;
}
$yourActivity = Workflow::newActivityStub(
YourActivityInterface::class,
ActivityOptions::new()->withScheduleToStartTimeout(60)
);
$this->currentState = 'waiting activity';
try{
yield $yourActivity->doSomething('some input');
} catch (\Throwable $e) {
$this->currentState = 'activity failed';
throw $e;
}
$this->currentState = 'done';
return null;
}
}
You can also issue a Query from code using the QueryWorkflow()
API on a Temporal Client object.
Use WorkflowStub
to Query Workflow instances from your Client code (can be applied to both running and closed Workflows):
$workflow = $workflowClient->newWorkflowStub(
YourWorkflow::class,
WorkflowOptions::new()
);
$workflowClient->start($workflow);
var_dump($workflow->getCurrentState());
sleep(60);
var_dump($workflow->getCurrentState());