PHP workflowDeferredHandlerStart feature flag
In PHP SDK v2, by default Workflow Handler runs before Signals and Updates (incorrect behavior). Since PHP SDK v2.11.0, workflowDeferredHandlerStart feature flag added to enhance behavior. Set flag to true to enable correct behavior where handlers respect initialization.
PHP Query from Client using WorkflowStub
To query a Workflow in PHP from Client: create a typed stub with $workflowClient->newWorkflowStub(WorkflowClass::class, WorkflowOptions::new()), then call the Query method like var_dump($workflow->getCurrentState());
PHP Workflow interface method attributes
All methods in a PHP Workflow interface must have one of these attributes: #[WorkflowMethod] for the entry point (required parameter executionStartToCloseTimeoutSeconds if not in attribute); #[SignalMethod] for methods that react to signals (must have void return type); #[QueryMethod] for methods that react to queries (must have non-void return type).
PHP dynamic Update handler registration
Register Update handlers dynamically in PHP using Workflow::registerUpdate(name: 'pause', handler: fn() => ..., validator: fn() => ...). The third argument is optional Update validator that must have same parameters as handler and throw exception on validation failure.
PHP handler unfinishedPolicy attribute
In PHP SDK, silence warnings about unfinished handler executions by passing unfinishedPolicy parameter to #[UpdateMethod] or #[SignalMethod] attributes: #[UpdateMethod(unfinishedPolicy: HandlerUnfinishedPolicy::Abandon)]
PHP Workflow::await() for wait conditions
PHP Workflow::await() is used in handlers and main Workflow to set a function that prevents code from proceeding until condition returns true. Used to wait until handler should start, or to wait for specific conditions to become true.
PHP Query handler restrictions
In PHP, Query handlers must not include logic that causes Command generation within a Query handler, such as executing Activities. Including such logic causes unexpected behavior.
PHP Query handler example with state tracking
PHP Query handlers use #[QueryMethod] attribute and return a value. They can track state changes from the main Workflow method. Example with custom Query type 'current_state': #[QueryMethod('current_state')] public function getCurrentState(): string { return $this->currentState; }
PHP Query error exceptions
PHP Query errors: WorkflowNotFoundException if no Worker polling Task Queue; WorkflowQueryException if something goes wrong during Query execution (any exception in Query handler triggers this).
PHP Mutex to prevent concurrent handler execution
Use Workflow\Mutex to coordinate access and prevent concurrent handler execution. Lock makes sure only one handler instance executes a section at a time. Use yield Workflow::runLocked($this->mutex, function() { ... }) to execute locked sections.
PHP #[WorkflowInit] example
PHP #[WorkflowInit] example: #[Workflow\WorkflowInit] public function __construct(string $input) { $this->nameWithTitle = 'Sir ' . $input; } Both constructor and #[WorkflowMethod] must have same parameters.
PHP Update validator with #[UpdateValidatorMethod]
In PHP SDK, use #[UpdateValidatorMethod(forUpdate: 'handlerName')] attribute to define an Update validator. The validator must accept the same input parameters as the Update handler and return void. It should throw an exception if validation fails.
PHP wait for handlers to finish with allHandlersFinished
Use Workflow::await(fn() => Workflow::allHandlersFinished()) in main Workflow method to ensure handlers complete before Workflow finishes. Prevents Workflow from completing while handlers are still waiting on async tasks.
PHP Query definition with attributes
In PHP SDK, define a Query using the #[QueryMethod] attribute on a Workflow interface method. The Query name is a string and must have a non-void return type. Query arguments must be serializable. Example: #[QueryMethod('status')] public function getStatus(): string;
PHP Update async accept with startUpdate
In PHP SDK, use WorkflowStub->startUpdate() method for async Update acceptance. Returns UpdateHandle to get result later without waiting. Processing Workflow Worker must be available or request may block indefinitely or fail due to timeout.
PHP startUpdate example with UpdateHandle
PHP startUpdate example: $handle = $stub->startUpdate('addGreeting', 'World'); $result = $handle->getResult(timeout: 2.5);
PHP Update-with-Start using updateWithStart
PHP updateWithStart API checks if Workflow with given ID exists. If exists, processes Update. If not, starts new Workflow and processes Update before main method executes. Returns once requested wait stage reached or times out.
PHP Update-with-Start requirements
For PHP Update-with-Start: provide WorkflowStub from WorkflowOptions with Workflow ID Conflict Policy specified (use 'UseExisting' with idempotent Update handler). Not all WorkflowOptions allowed (e.g., Cron Schedule causes error). Workflow ID optional in Update but if specified must match WorkflowOptions ID.
PHP Signal handler with Workflow::await()
PHP Signal handlers use #[SignalMethod] attribute. The Workflow main coroutine waits for state changes using Workflow::await(fn() => condition). Example: yield Workflow::await(fn() => $this->value);
PHP Signal definition with attributes
In PHP SDK, define a Signal using the #[SignalMethod] attribute on a Workflow interface method. The Signal name is a string. Signal arguments must be serializable. Example: #[SignalMethod] public function setValue(bool $value): void;
PHP Signal error: ServiceClientException
When sending a Signal from PHP Client, the only exception that results from Signal execution is ServiceClientException. All handlers may experience additional exceptions during initial (pre-Worker) part of handler request lifecycle.
PHP Mutex example in handler
PHP Mutex example: $this->mutex = new Workflow\Mutex(); In handler: yield Workflow::runLocked($this->mutex, function () use ($data) { $this->x = $data->x; yield Workflow::timer(1); $this->y = $data->y; });
PHP send Signal from Client to running Workflow
To send a Signal to an already-running Workflow in PHP, use $workflowClient->newRunningWorkflowStub(WorkflowClass::class, 'workflowID') to get a stub, then call the Signal method like $workflow->setValue(true);
PHP Update definition with #[UpdateMethod]
In PHP SDK, define an Update using the #[UpdateMethod] attribute. The Update name (type) is a string. Update arguments and response must be serializable. The method can return void or a serializable value. Example: #[UpdateMethod] public function pauseProcessing(): void;
PHP Update method custom name
In PHP SDK, the Update type defaults to the method name. To assign a custom Update type, use #[UpdateMethod(name: 'customName')] attribute parameter.
PHP #[WorkflowInit] attribute on constructor
Use #[WorkflowInit] attribute on Workflow constructor to receive the same Workflow parameters as #[WorkflowMethod]. SDK ensures constructor receives Workflow input arguments that Client sent. Constructor parameters are also passed to #[WorkflowMethod]. Useful if message handlers need access to Workflow input.
PHP Signal-With-Start using startWithSignal
PHP startWithSignal API sends a Signal if a Workflow with given ID is running, or starts a new Workflow and delivers the Signal to it. Example: $run = $workflowClient->startWithSignal($workflow, 'setValue', [true], []);
PHP Update handler with #[UpdateMethod]
PHP Update handlers use #[UpdateMethod] attribute. Unlike Query handlers, Update handlers can change Workflow state. The method can return void or a serializable value. Handler method can accept multiple serializable parameters but single parameter is recommended.
PHP send Update from Client to Workflow
To send an Update to a Workflow in PHP: create typed stub with $workflowClient->newWorkflowStub(WorkflowClass::class, $workflowOptions), start Workflow with $run = $workflowClient->start($workflow), then call Update method like $count = $workflow->addGreeting('World');
PHP Update error: no Workers polling Task Queue
When sending Update in PHP and no Workflow Workers polling Task Queue, request retried indefinitely by SDK Client. Configure RPC timeout to impose timeout, raising WorkflowUpdateRPCTimeoutOrCanceledException.
PHP Update error: WorkflowUpdateException
PHP Update errors: WorkflowUpdateException raised when Update rejected by validator, or when Update fails after acceptance (like failed Child Workflow, failed Activity with finite retries, or ApplicationFailure).
PHP Update error: Workflow Task Failure
PHP Update Workflow Task Failure: if request hasn't been accepted by server, receive WorkflowUpdateException; if request accepted (durable), use UpdateHandle to fetch result once Workflow healthy after code deploy.
PHP Update error: Workflow finished during Update
PHP Update error WorkflowUpdateException raised if Workflow finished while Update handler execution in progress (Workflow canceled/failed, or completed normally and author didn't wait for handlers to finish).
PHP dynamic Query with registerDynamicQuery
Register dynamic Query handler in PHP using Workflow::registerDynamicQuery(function (string $name, ValuesInterface $arguments): string { ... }). Invoked at runtime if no other Query with same name registered.
PHP dynamic Signal with registerDynamicSignal
Register dynamic Signal handler in PHP using Workflow::registerDynamicSignal(function (string $name, ValuesInterface $arguments): void { ... }). Invoked at runtime if no other Signal with same name registered.
PHP dynamic Update with registerDynamicUpdate
Register dynamic Update handler in PHP using Workflow::registerDynamicUpdate(handler, validator). Both handler and validator must accept string name and ValuesInterface arguments. Validator is optional and should throw exception on validation failure.
PHP dynamic Update example
PHP dynamic Update example: Workflow::registerDynamicUpdate(static fn(string $name, ValuesInterface $arguments): string => sprintf('Got update `%s` with %d arguments', $name, $arguments->count()), static fn(string $name, ValuesInterface $arguments) => str_starts_with($name, 'update_') or throw new InvalidArgumentException('Invalid update name'));
PHP dynamic handlers best practice
Dynamic Handlers (Queries, Signals, Updates) should be used judiciously as fallback mechanism, not primary approach. Define handlers statically whenever possible with clear names. Reserve dynamic handlers for cases where handler names not known at development time.
PHP Update-with-Start example
PHP Update-with-Start example: $stub = $workflowClient->newUntypedWorkflowStub(ShoppingCartWorkflow::class, WorkflowOptions::new()->withTaskQueue('service-queue')->withWorkflowId($cartId)->withWorkflowIdConflictPolicy(WorkflowIdConflictPolicy::UseExisting)); $handle = $workflowClient->updateWithStart(workflow: $stub, update: 'addItem', updateArgs: [$itemId, $quantity]); $price = $handle->getResult();
Message handler error: workflow does not exist
When sending a Signal, Update, or Query to a non-existent Workflow, you receive a temporalio.service.RPCError exception where the status attribute is RPCStatusCode.NOT_FOUND.
Caution with dynamic components
Use dynamic elements judiciously and as a fallback mechanism, not a primary design. They can introduce long-term maintainability and debugging issues. Reserve dynamic invocation use for cases where a name is not or can't be known at compile time. Prefer to use compiler-checked type-safe arguments rather than component name strings when possible.
Dynamic Activity definition
A dynamic Activity is a stand-in implementation used when an Activity Task with an unknown Activity type is received by the Worker. Add dynamic=True to the @activity.defn decorator. Register the Activity with the Worker. The Activity Definition must accept a single argument of type Sequence[temporalio.common.RawValue]. Use a payload_converter function to convert RawValue objects to required types.
Dynamic Workflow definition
A dynamic Workflow is a special stand-in Workflow Definition used when an unknown Workflow Execution request arrives. Add dynamic=True to the @workflow.defn decorator. Register the dynamic Workflow with the Worker. The primary Workflow method must accept a single argument of type Sequence[temporalio.common.RawValue]. Use a payload_converter function to convert RawValue objects to required types.
Non-type-safe message handler invocation
When you don't have access to Workflow Definition method signatures, you can send messages using non-type-safe APIs by passing method names as strings instead of method objects. Use string names with: Client.start_workflow, WorkflowHandle.query, WorkflowHandle.signal, WorkflowHandle.execute_update, WorkflowHandle.start_update. Use non-type-safe APIs: Client.get_workflow_handle (instead of get_workflow_handle_for), and workflow.get_external_workflow_handle (instead of get_external_workflow_handle_for).
Use @workflow.init decorator for Workflow input access in handlers
Add the @workflow.init decorator to the __init__ method to give it the same Workflow parameters as the @workflow.run method. The SDK ensures __init__ receives the Workflow input arguments sent by the Client. This is useful when message handlers need access to Workflow input. The __init__ and @workflow.run methods must have the same parameters with the same type annotations.
workflow.wait_condition use cases
workflow.wait_condition(lambda: condition, timeout) prevents code from proceeding until a condition is true. Three important use cases: (1) Wait for a Signal or Update to arrive before proceeding in the main Workflow. (2) Wait in a handler until it's appropriate to continue. (3) Wait in the main Workflow until all active handlers have finished using workflow.all_handlers_finished.
Using asyncio.Lock to coordinate async handler execution
Use asyncio.Lock to prevent concurrent handler execution and ensure atomicity of multi-step operations. Initialize the lock in __init__ as self.lock = asyncio.Lock(). In handler code, wrap critical sections with async with self.lock: to ensure only one handler instance can execute that section at any given time.
Async message handlers concurrent execution risks
Signal and Update handlers can be async def, allowing concurrent execution with the main Workflow method. When multiple handler instances run simultaneously, they may interact in unpredictable ways if not properly coordinated. Concurrent processes can read partial state updates if handlers modify multiple fields without synchronization. Use asyncio.Lock to prevent concurrent handler execution of critical sections.
Send Update using start_update
Call WorkflowHandle.start_update to start an Update and receive an UpdateHandle as soon as the Update is accepted (or at another specified stage). This is useful for async def Update handlers that perform long-running operations. Use the UpdateHandle later to fetch results with await update_handle.result(). The wait_for_stage parameter can specify whether to wait until ACCEPTED or other stages.
Send Update using execute_update
Call WorkflowHandle.execute_update to send an Update and wait for it to complete. This method blocks until the Update completes and returns the result. Example: previous_language = await workflow_handle.execute_update(GreetingWorkflow.set_language, Language.Chinese)
Signal-With-Start functionality
Signal-With-Start allows a Client to send a Signal to a Workflow Execution, starting the Execution if it is not already running. Use the start_workflow method and pass the start_signal argument with the name of your Signal, along with start_signal_args containing the Signal arguments.
Send Signal using WorkflowHandle.signal
Use WorkflowHandle.signal to send a Signal to a Workflow Execution from a Temporal Client. The call returns when the server accepts the Signal; it does not wait for the Signal to be delivered to the Workflow Execution. Example: await workflow_handle.signal(GreetingWorkflow.approve, ApproveInput(name="me"))
Send Query using WorkflowHandle.query
Use WorkflowHandle.query to send a Query to a Workflow Execution. Example: supported_languages = await workflow_handle.query(GreetingWorkflow.get_languages, GetLanguagesInput(supported_only=True))
Dynamic Signal, Query, or Update handler definition
A dynamic Signal, Query, or Update handler is a special stand-in handler used when an unregistered handler request arrives. Add dynamic=True to the handler decorator (@workflow.signal(dynamic=True), etc.). The handler signature must accept (self, name: str, args: Sequence[RawValue]). Use a payload_converter function to convert RawValue objects to required types. The handler is responsible for transforming the sequence contents into usable data.
Message handler serialization requirements
The parameters and return values of message handlers (Query, Signal, Update) and the main Workflow function must be serializable. Prefer data classes to multiple input parameters. Data class parameters allow you to add fields without changing the calling signature. Keep in mind that serialization and deserialization can fail with the default data converter if the new field does not have a default value.
Update validator definition and behavior
Update validators are defined using the @<update-handler-name>.validator decorator on a method that accepts the same argument types as the handler and returns None. Validators are always optional. Use validators to reject an Update before it is written to History. To reject an Update, raise any type of exception in the validator. Without a validator, Updates are always accepted. When a Validator raises an error, the Update is rejected and WorkflowExecutionUpdateAccepted will not be added to Event History. The caller receives an 'Update failed' error. Update validators cannot be async def.
Update handler definition and behavior
An Update handler is defined using the @workflow.update decorator. Update handlers can mutate Workflow state and return a value. Update handlers can be async def, allowing use of Activities, Child Workflows, asyncio.sleep Timers, workflow.wait_condition conditions, and more. An Update is a trackable synchronous request where the sender must wait until the Worker accepts or rejects it, and may wait further to receive a returned value or exception. WorkflowExecutionUpdateAccepted is added to Event History when the Worker confirms the Update passed validation. WorkflowExecutionUpdateCompleted is added to Event History when the Worker confirms the Update has finished.
Message handler error: client can't contact server
When a client can't contact the server when sending a Signal, Update, or Query, you receive a temporalio.service.RPCError exception where the status attribute is RPCStatusCode.UNAVAILABLE (after some retries; see the retry_config argument to Client.connect).
Signal handler definition and behavior
A Signal handler is defined using the @workflow.signal decorator. Signal handlers mutate Workflow state but cannot return a value. Signal handlers can be async def, allowing use of Activities, Child Workflows, asyncio.sleep Timers, workflow.wait_condition conditions, and more. The response to a Signal is sent immediately from the server without waiting for the Workflow to process it. The WorkflowExecutionSignaled Event appears in the Workflow's Event History when a Signal is sent. Signals can only be sent to Workflow Executions that haven't closed.
Query handler definition and behavior
A Query handler is defined using the @workflow.query decorator. Query handlers use def (synchronous), not async def. A Query handler must inspect Workflow state but cannot mutate it. Query handlers can accept arguments through the decorator and return a value. Sending a Query does not add events to a Workflow's Event History. You can send Queries to closed Workflow Executions within a Namespace's Workflow retention period, including Workflows that have completed, failed, or timed out. Querying terminated Workflows is not supported. A Worker must be online and polling the Task Queue to process a Query.