Temporal Client - PHP SDK
This guide introduces Temporal Clients. It explains the role and use of Clients and shows you how to configure your PHP Client code to connect to the Temporal Service.
The pages shows how to do the following:
- Connect to a local development Temporal Service
- Connect to Temporal Cloud
- Start a Workflow Execution
- Advanced connection options
How to connect a Temporal Client to a Temporal Service
A Temporal Client enables you to communicate with the Temporal Service. Communication with a Temporal Service includes, but isn't limited to, the following:
- Scheduling Workflow Executions.
- Starting Workflow Executions.
- Sending Signals to Workflow Executions.
- Sending Queries to Workflow Executions.
- Sending Updates to Workflow Executions.
- Getting the results of a Workflow Execution.
- Providing an Activity Task Token.
A Temporal Client cannot be initialized and used inside a Workflow. However, it is acceptable and common to use a Temporal Client inside an Activity to communicate with a Temporal Service.
When you are running a Temporal Service locally (such as the Temporal CLI), the number of connection options you must provide is minimal.
Many SDKs default to the local host or IP address and port that Temporalite and Docker Compose serve (127.0.0.1:7233
).
In the PHP SDK, different client classes are responsible for different functional areas.
The ServiceClient
is responsible for the low-level API and connection to the Temporal Service.
It is also used in higher-level clients: WorkflowClient
and ScheduleClient
.
RoadRunner is not required to work only with the client API; however, the gRPC extension is necessary.
Use create()
factory methods to create clients.
use Temporal\Client\GRPC\ServiceClient;
use Temporal\Client\WorkflowClient;
$serviceClient = ServiceClient::create('localhost:7233');
$workflowClient = WorkflowClient::create($serviceClient);
// Use $workflowClient to work with Workflows ...
See the Advanced connection options section for more information on configuring the connection.
How to connect a Temporal Client to a Temporal Cloud
When you connect to Temporal Cloud, you need to provide additional connection and client options that include the following:
- The Temporal Cloud Namespace Id.
- The Namespace's gRPC endpoint. An endpoint listing is available at the Temporal Cloud Website on each Namespace detail page. The endpoint contains the Namespace Id and port.
- mTLS CA certificate.
- mTLS private key.
For more information about managing and generating client certificates for Temporal Cloud, see How to manage certificates in Temporal Cloud.
For more information about configuring TLS to secure inter- and intra-network communication for a Temporal Service, see Temporal Customization Samples.
Use the ServiceClient::createSSL()
method to configure a client connection to the Temporal Service.
The $clientKey
argument must be combined with the $clientPem
to authenticate the Client.
use Temporal\Client\ClientOptions;
use Temporal\Client\GRPC\ServiceClient;
use Temporal\Client\WorkflowClient;
$serviceClient = \Temporal\Client\GRPC\ServiceClient::createSSL(
address: '<your-custom-namespace>.tmprl.cloud:7233',
// crt: 'certs/server-root-ca-cert.pem', # ROOT CA to validate the server cert
clientKey: 'certs/client-private-key.pem',
clientPem: 'certs/client-cert.pem',
// overrideServerName: 'tls-sample',
);
$workflowClient = WorkflowClient::create(
serviceClient: $serviceClient,
options: (new ClientOptions())
->withNamespace('<your-custom-namespace>.<id>'),
);
To run Worker processes managed by Temporal Cloud, configure RoadRunner in the same way.
temporal:
# ...
tls:
# root_ca: 'certs/server-root-ca-cert.pem'
key: 'certs/client-private-key.pem'
cert: 'certs/client-cert.pem'
client_auth_type: require_and_verify_client_cert
# server_name: 'tls-sample'
To set up the API key in the Client, use the ServiceClient::withAuthKey()
method:
$serviceClient = \Temporal\Client\GRPC\ServiceClient::createSSL(/*...*/)
->withAuthKey('your-api-key');
How to start a Workflow Execution
Workflow Execution semantics rely on several parameters—that is, to start a Workflow Execution you must supply a Task Queue that will be used for the Tasks (one that a Worker is polling), the Workflow Type, language-specific contextual data, and Workflow Function parameters.
In the examples below, all Workflow Executions are started using a Temporal Client. To spawn Workflow Executions from within another Workflow Execution, use either the Child Workflow or External Workflow APIs.
See the Customize Workflow Type section to see how to customize the name of the Workflow Type.
A request to spawn a Workflow Execution causes the Temporal Service to create the first Event (WorkflowExecutionStarted) in the Workflow Execution Event History. The Temporal Service then creates the first Workflow Task, resulting in the first WorkflowTaskScheduled Event.
Use Workflow stub to start a Workflow Execution from within a Client.
Workflow stub is a proxy generated by the WorkflowClient
.
You can use a typed or untyped Workflow stub in the client code.
- Typed Workflow stubs are useful because they are type safe and allow you to invoke your Workflow methods such as
#[WorkflowMethod]
,#[QueryMethod]
,#[SignalMethod]
, and#[UpdateMethod]
directly. - An untyped Workflow stub does not use a Workflow interface. It is more flexible because it has methods from the
WorkflowStubInterface
, such asstart
,signal
,getResults
,query
,signal
,update
,cancel
,terminate
, etc. When using untyped Workflow stub, we rely on the Workflow Type, Activity Type, Child Workflow Type, as well as Query and Signal names.
For example, there is a Workflow defined as follows:
#[WorkflowInterface]
interface AccountTransferWorkflowInterface
{
#[WorkflowMethod(name: "account.transfer")]
public function begin(UuidInterface $transactionId);
#[UpdateMethod(name: "pay")]
public function move(UuidInterface $from, UuidInterface $to, int $amount);
#[UpdateMethod(name: "finish")]
public function commit();
#[UpdateMethod(name: "cancel")]
public function rollback(string $reason);
}
In case of a typed Workflow stub, you can use the AccountTransferWorkflowInterface
to call the Workflow methods directly:
$stub = $workflowClient->newWorkflowStub(AccountTransferWorkflowInterface::class);
$workflowClient->start($stub, $transactionId);
$stub->move($from1, $to1, $amount1);
$stub->move($from2, $to2, $amount2);
$stub->commit();
In case of an untyped Workflow stub, you need to specify Workflow Type and method names explicitly:
$stub = $workflowClient->newUntypedWorkflowStub('account.transfer');
$workflowClient->start($stub, $transactionId);
$stub->update('pay', $from1, $to1, $amount1);
$stub->update('pay', $from2, $to2, $amount2);
$stub->update('finish');
A Workflow Execution can be started either synchronously or asynchronously.
Synchronous start
A synchronous start initiates a Workflow and then waits for its completion. The started Workflow will not rely on the invocation process and will continue executing even if the waiting process crashes or stops.
Be sure to acquire the Workflow interface or class name you want to start. For example:
#[WorkflowInterface]
interface AccountTransferWorkflowInterface
{
#[WorkflowMethod(name: "MoneyTransfer")]
#[ReturnType(UuidInterface::class)]
public function transfer( string $fromAccountId, string $toAccountId, string $referenceId, int $amountCents);
}
To start the Workflow in sync mode:
$accountTransfer = $workflowClient->newWorkflowStub(
AccountTransferWorkflowInterface::class,
);
$result = $accountTransfer->transfer('fromID', 'toID', 'refID', 1000);
Asynchronous start
An asynchronous start initiates a Workflow Execution and immediately returns to the caller without waiting for a result. This is the most common way to start Workflows in a live environment.
To start a Workflow asynchronously, pass the Workflow stub instance and start parameters into the WorkflowClient::start()
method.
$accountTransfer = $workflowClient->newWorkflowStub(
AccountTransferWorkflowInterface::class,
);
$run = $this->workflowClient->start($accountTransfer, 'fromID', 'toID', 'refID', 1000);
After the Workflow is started, you can receive details about the Workflow Execution or result via the WorkflowRun
object methods:
$run = $workflowClient->start($accountTransfer, 'fromID', 'toID', 'refID', 1000);
// Get the Workflow ID
var_dump($run->getExecution()->getID());
// Describe the Workflow Execution
var_dump($run->describe());
// Wait for the Workflow to complete and get the result with 10-second timeout
var_dump($run->getResult(timeout: 10));
Recurring start
You can start a Workflow Execution on a regular schedule with the CronSchedule option.
How to set a Workflow's Task Queue
In most SDKs, the only Workflow Option that must be set is the name of the Task Queue.
When developing in PHP, the Task Queue name defaults to "default"
.
While setting a meaningful Task Queue name is recommended for better observability, Workflows can be run without setting this option.
PHP's default is different from most SDKs, which do require an explicit Task Queue name.
For your code to execute, a Worker Process must be running (how to run Worker Processes). This process needs a Worker Entity that is polling the same Task Queue name.
Set the Workflow Task Queue with the Workflow stub in the Client code using WorkflowOptions::withTaskQueue()
.
$stub = $workflowClient->newWorkflowStub(
YourWorkflowInterface::class,
WorkflowOptions::new()
->withTaskQueue("Workflow-Task-Queue-1"),
);
How to set a Workflow Id
Although it is not required, we recommend providing your own Workflow Id that maps to a business process or business entity identifier, such as an order identifier or customer identifier.
Set the Workflow Id with the Workflow stub in the Client code using WorkflowOptions::withTaskQueue()
.
$stub = $workflowClient->newWorkflowStub(
YourWorkflowInterface::class,
WorkflowOptions::new()
->withWorkflowId("Workflow-Id"),
);
How to get the results of a Workflow Execution
If the call to start a Workflow Execution is successful, you will gain access to the Workflow Execution's Run Id.
The Workflow Id, Run Id, and Namespace may be used to uniquely identify a Workflow Execution in the system and get its result.
It's possible to both block progress on the result (synchronous execution) or get the result at some other point in time (asynchronous execution).
In the Temporal Platform, it's also acceptable to use Queries as the preferred method for accessing the state and results of Workflow Executions.
If you need to wait for the completion of a Workflow after an asynchronous start, make a blocking call to
the WorkflowRun::getResult()
method.
$stub = $workflowClient->newWorkflowStub(YourWorkflowInterface::class);
$run = $workflowClient->start($stub, 'fromID', 'toID', 'refID', 1000);
var_dump($run->getResult());
In case of untyped Workflow stub, you can use the WorkflowStub::getResult()
method:
$stub = $workflowClient->newUntypedWorkflowStub('account.transfer');
$workflowClient->start($stub, 'fromID', 'toID', 'refID', 1000);
var_dump($stub->getResult(timeout: 5.5));
Note that you can specify a timeout for the getResult()
method in seconds.
If the Workflow does not complete within the specified time, a TimeoutException
will be thrown.
See how to limit all RPC calls in the RPC timeout section.
Advanced connection options
In PHP, it is common practice to work with resources in blocking mode. Long blocks can quickly exhaust the pool of available workers and lead to application failure.
This section introduces features and configuration examples of the PHP SDK when working with the Temporal Client API.
Connection
gRPC connections in the PHP SDK are lazy by default, meaning they are not established until the first call.
To force establishing the connection to the Temporal Service, you can call the ConnectionInterface::connect()
or ServiceClient::getServerCapabilities()
method.
// ...
$serviceClient->getConnection()->connect(timeout: 10);
// or
$serviceClient->getServerCapabilities();
If, for some reason, the established connection is broken, the SDK will automatically attempt to restore it, taking into account the configured retry policy.
Retry policy
Whenever the client fails to connect to the server, an error with a status code is generated.
If the status code is UNKNOWN
, UNAVAILABLE
, or RESOURCE_EXHAUSTED
, the client will make another connection attempt.
By default, the number of attempts is unlimited, and the interval between them will range from 0.5 to 100 seconds with a backoff coefficient of 2.
This means that the client will likely be blocked until it establishes a connection to the server through infinite attempts.
If you want to change the default behavior, use the withRetryPolicy()
method when creating a client service:
use Temporal\Client\Common\RpcRetryOptions;
use Temporal\Client\GRPC\ServiceClient;
use Temporal\Client\WorkflowClient;
$serviceClient = ServiceClient::create('localhost:7233');
$workflowClient = WorkflowClient::create($serviceClient)
->withRetryOptions(
RpcRetryOptions::new()
->withMaximumAttempts(10)
->withInitialInterval('1 second') // The first retry will be in 1 second
->withBackoffCoefficient(2.5) // Each next retry time will be multiplied by 2.5
->withMaximumInterval('20 seconds') // The maximum interval between attempts
->withMaximumJitterCoefficient(0.2) // Actual retry time can be +/- 20% of the calculated time
);
RPC timeout
When the client calls the service's RPC, there is no default time limit for waiting for a response. This can result in the code call $result = $workflowHandle->getResult();
blocking the PHP worker until the Workflow completes. In some cases, this is not the desired behavior, and there may be a need to set a reasonable timeout for waiting for the RPC to complete.
Use the withTimeout()
method to build a client with a timeout for all RPC calls.
use Temporal\Client\GRPC\ServiceClient;
use Temporal\Client\WorkflowClient;
$serviceClient = ServiceClient::create('localhost:7233');
$workflowClient = WorkflowClient::create($serviceClient)
->withTimeout(5.75);
// Create a Workflow stub
$stub = $workflowClient->newWorkflowStub(AccountTransferWorkflowInterface::class);
// If the Workflow does not complete within 5.75 seconds, a TimeoutException will be thrown
$result = $stub->transfer('fromID', 'toID', 'refID', 1000);
The withTimeout()
method is immutable.
If you need to change the timeout for individual operations, create a new client from the existing one with a specific timeout: $newClient = $workflowClient->withTimeout(0);
(0
means no timeout).