temporal/sdk
Temporal PHP SDK for building durable, scalable workflow orchestration with Temporal. Author Workflows and Activities in PHP, run them with RoadRunner workers, and manage executions via gRPC clients. Composer-installable with optional protobuf for performance.
Install the SDK:
composer require temporal/sdk
Ensure your composer.json includes "temporal/sdk": "^x.y.z" (check releases).
Configure the Client:
Add Temporal server connection details to your .env:
TEMPORAL_SERVER_URL=temporal://localhost:7233
TEMPORAL_NAMESPACE=default
Initialize the client in a service provider (e.g., AppServiceProvider):
use Temporal\Client;
$client = Client::create([
'connection' => [
'address' => env('TEMPORAL_SERVER_URL'),
],
]);
First Workflow Execution: Define a simple workflow and activity:
// app/Workflows/HelloWorkflow.php
use Temporal\Workflow;
Workflow::define('hello-workflow', function () {
$result = Workflow::executeActivity('say-hello', ['name' => 'World']);
return $result;
});
// app/Activities/SayHello.php
use Temporal\Activity;
Activity::define('say-hello', function (string $name) {
return "Hello, {$name}!";
});
Run a Worker:
Start a worker in a console command (e.g., php artisan temporal:workers):
use Temporal\Worker;
use Temporal\WorkerFactory;
$factory = WorkerFactory::create($client);
$worker = $factory->createWorker('default-task-queue');
$worker->registerWorkflowTypes([HelloWorkflow::class]);
$worker->registerActivityTypes([SayHello::class]);
$worker->run();
Trigger the Workflow: Use the client to start the workflow:
$handle = $client->workflowService()->startWorkflow(
'hello-workflow',
[],
['taskQueue' => 'default-task-queue']
);
$result = $handle->getResult();
Workflow Design:
Workflow::define() for declarative workflows.Workflow::define('parent-workflow', function () {
$childResult = Workflow::executeChildWorkflow('child-workflow', []);
return "Parent: {$childResult}";
});
Workflow::onSignal('pause', function () {
Workflow::sleep(1000); // Pause execution
});
Activity Patterns:
Activity::define('faulty-activity', function () {
// ...
}, ['retry' => ['maximumAttempts' => 3, 'interval' => '1s']]);
Activity::define('long-running-task', function () {
while (true) {
Workflow::heartbeat('Still running...');
sleep(1);
}
});
Workflow Execution:
$promises = [];
foreach ($tasks as $task) {
$promises[] = Workflow::executeActivity('process-task', [$task]);
}
return Workflow::allOf($promises);
Workflow::define('trackable-workflow', function () {
$this->query('status', function () {
return $this->status;
});
});
Service Container Binding:
Bind the client/worker factory in AppServiceProvider:
$this->app->singleton(Client::class, function ($app) {
return Client::create(['connection' => ['address' => env('TEMPORAL_SERVER_URL')]]);
});
Artisan Commands: Create a command to manage workers:
// app/Console/Commands/RunTemporalWorkers.php
use Illuminate\Console\Command;
use Temporal\WorkerFactory;
class RunTemporalWorkers extends Command
{
protected $signature = 'temporal:workers';
public function handle()
{
$factory = WorkerFactory::create(app(Client::class));
$worker = $factory->createWorker('default-task-queue');
$worker->registerWorkflowTypes([HelloWorkflow::class]);
$worker->run();
}
}
Queues as Fallback: Use Laravel queues for non-critical tasks while offloading complex orchestration to Temporal:
// Dispatch a workflow instead of a queue job
$handle = app(Client::class)->workflowService()->startWorkflow(
'process-order',
['orderId' => $order->id],
['taskQueue' => 'orders']
);
Event Listeners: Trigger workflows from Laravel events:
// app/Listeners/StartOrderWorkflow.php
use Temporal\Client;
public function handle(OrderCreated $event)
{
$client = app(Client::class);
$client->workflowService()->startWorkflow(
'process-order',
['orderId' => $event->order->id],
['taskQueue' => 'orders']
);
}
Serialization Issues:
JsonSerializable or use Temporal\Payloads.Worker Registration:
// ❌ Missing registration
$worker->run(); // Workflows/activities won't execute!
registerWorkflowTypes() and registerActivityTypes().Task Queue Mismatch:
startWorkflow() and createWorker().Workflow Timeout:
$handle = $client->workflowService()->startWorkflow(
'long-workflow',
[],
['taskQueue' => 'queue', 'workflowId' => 'unique-id', 'timeout' => '1h']
);
Activity Heartbeats:
Workflow::heartbeat() for long-running activities.Temporal CLI:
temporal workflow describe <workflow_id> --namespace <namespace>
temporal activity list --namespace <namespace>
temporal workflow list --namespace <namespace> --query "WorkflowType = 'hello-workflow'"
Logging:
$client = Client::create([
'connection' => ['address' => env('TEMPORAL_SERVER_URL')],
'logger' => new Monolog\Logger('temporal', [new Monolog\Handler\StreamHandler('php://stderr')]),
]);
Local Testing:
docker run -p 7233:7233 temporalio/auto-setup:latest
Custom Interceptors:
$client->workflowService()->addInterceptor(new CustomWorkflowInterceptor());
Signal Handling:
Workflow::define('custom-workflow', function () {
Workflow::onSignal('custom-signal', function ($data) {
// Handle signal
});
});
Activity Delegates:
Activity::define('external-call', function ($
How can I help you explore Laravel packages today?