PHP SDK guide

Your first workflow

Start a runtime, install the published SDK, run its shipped worker and client, and check one durable result. Each stage ends with something you can observe.

What you need

1. Install the published SDK

mkdir durable-php-quickstart
cd durable-php-quickstart
composer init --name=acme/durable-php-quickstart --no-interaction
composer require durable-workflow/sdk:^2.0@RC
Checkpoint

composer show durable-workflow/sdk reports a 2.0 prerelease release.

2. Start or select the runtime

Choose one path and keep its values in both terminals.

Self-hosted Server on your machine

Bootstrap a durable volume once, then start the Server version qualified with this SDK:

export DW_SERVER_IMAGE="$(
  curl -fsSL https://php.durable-workflow.com/quickstart-contract.json \
    | php -r '$contract = json_decode(stream_get_contents(STDIN), true, 512, JSON_THROW_ON_ERROR); echo $contract["runtime_targets"]["server"]["image"];'
)"
export DW_AUTH_TOKEN=dev-token

docker volume create durable-workflow-php-quickstart
docker run --rm \
  -v durable-workflow-php-quickstart:/app/database \
  -e DW_AUTH_DRIVER=token \
  -e DW_AUTH_TOKEN="$DW_AUTH_TOKEN" \
  "$DW_SERVER_IMAGE" server-bootstrap

docker run -d --name durable-workflow-php-server \
  -p 8080:8080 \
  -v durable-workflow-php-quickstart:/app/database \
  -e DW_AUTH_DRIVER=token \
  -e DW_AUTH_TOKEN="$DW_AUTH_TOKEN" \
  "$DW_SERVER_IMAGE"

until curl -sf http://localhost:8080/api/ready >/dev/null; do sleep 1; done
export DURABLE_WORKFLOW_RUNTIME_URL='http://localhost:8080'
export DURABLE_WORKFLOW_NAMESPACE='default'
export DURABLE_WORKFLOW_CLIENT_TOKEN="$DW_AUTH_TOKEN"
export DURABLE_WORKFLOW_WORKER_TOKEN="$DW_AUTH_TOKEN"
Checkpoint

The readiness loop exits successfully. Pass the bare Server origin to the SDK; it appends its own /api segment.

Durable Workflow Cloud

Use the complete namespace runtime URI and namespace returned by provisioning. Do not replace the runtime URI with the Cloud control-plane URL or trim its path prefix.

export DURABLE_WORKFLOW_RUNTIME_URL='https://cloud.example/api/runtime/v1/namespaces/<runtime-id>'
export DURABLE_WORKFLOW_NAMESPACE='<provisioned-namespace>'

read -rsp 'Client credential: ' DURABLE_WORKFLOW_CLIENT_TOKEN; echo
export DURABLE_WORKFLOW_CLIENT_TOKEN
read -rsp 'Worker credential: ' DURABLE_WORKFLOW_WORKER_TOKEN; echo
export DURABLE_WORKFLOW_WORKER_TOKEN

The prompts do not echo credentials. Keep them in process environments or a secret manager, never in source or diagnostics.

3. Choose an isolated task queue

export DURABLE_WORKFLOW_TASK_QUEUE="php-quickstart-$(php -r 'echo bin2hex(random_bytes(8));')"
Checkpoint

printf '%s\n' "$DURABLE_WORKFLOW_TASK_QUEUE" prints a non-empty, unique queue name.

4. Copy the shipped files

These are the executable files published under the package's examples/ directory. Save each beside the new project's vendor/ directory.

bootstrap.php — locate Composer without a checkout-specific path
bootstrap.php
<?php

declare(strict_types=1);

(static function (): void {
    $candidates = array_unique([
        __DIR__.'/vendor/autoload.php',
        dirname(__DIR__).'/vendor/autoload.php',
        dirname(__DIR__, 2).'/vendor/autoload.php',
        dirname(__DIR__, 3).'/autoload.php',
        getcwd().'/vendor/autoload.php',
    ]);

    foreach ($candidates as $candidate) {
        if (is_file($candidate)) {
            require $candidate;

            return;
        }
    }

    throw new RuntimeException(
        'Composer autoload.php was not found. Run the example from a Composer project or copy all three example files beside vendor/.',
    );
})();

function quickstartEnvironment(string $name): string
{
    $value = getenv($name);
    if (!is_string($value) || trim($value) === '') {
        throw new RuntimeException("Set the {$name} environment variable before running this example.");
    }

    return trim($value);
}
worker.php — discover the attributed workflow and activity before polling
worker.php
<?php

declare(strict_types=1);

require __DIR__.'/bootstrap.php';

use DurableWorkflow\Attribute\Activity;
use DurableWorkflow\Attribute\Workflow;
use DurableWorkflow\Client;
use DurableWorkflow\Worker;
use DurableWorkflow\Worker\ActivityContext;
use DurableWorkflow\Worker\WorkflowContext;

final class GreeterWorkflow
{
    #[Workflow('quickstart.php.greeter')]
    public function run(WorkflowContext $context, string $name): array
    {
        $greeting = $context->activity('quickstart.php.greet', [$name]);

        return ['greeting' => $greeting];
    }
}

final class GreetingActivities
{
    #[Activity('quickstart.php.greet')]
    public function greet(ActivityContext $context, string $name): string
    {
        return "hello, {$name}";
    }
}

$client = new Client(
    quickstartEnvironment('DURABLE_WORKFLOW_RUNTIME_URL'),
    namespace: quickstartEnvironment('DURABLE_WORKFLOW_NAMESPACE'),
    workerToken: quickstartEnvironment('DURABLE_WORKFLOW_WORKER_TOKEN'),
);

Worker::create($client, quickstartEnvironment('DURABLE_WORKFLOW_TASK_QUEUE'))
    ->register(GreeterWorkflow::class, GreetingActivities::class)
    ->run();
client.php — start a unique workflow and wait for its result
client.php
<?php

declare(strict_types=1);

require __DIR__.'/bootstrap.php';

use DurableWorkflow\Client;

$client = new Client(
    quickstartEnvironment('DURABLE_WORKFLOW_RUNTIME_URL'),
    namespace: quickstartEnvironment('DURABLE_WORKFLOW_NAMESPACE'),
    controlToken: quickstartEnvironment('DURABLE_WORKFLOW_CLIENT_TOKEN'),
);

$workflowId = 'php-quickstart-'.bin2hex(random_bytes(16));
$handle = $client->startWorkflow(
    workflowType: 'quickstart.php.greeter',
    workflowId: $workflowId,
    taskQueue: quickstartEnvironment('DURABLE_WORKFLOW_TASK_QUEUE'),
    input: ['PHP'],
);

$result = $handle->result(timeoutSeconds: 90, pollIntervalSeconds: 1);

echo json_encode(
    ['workflow_id' => $workflowId, 'result' => $result],
    JSON_THROW_ON_ERROR | JSON_UNESCAPED_SLASHES,
).PHP_EOL;

5. Run the worker, client, and result read

In terminal one, expose only the worker role credential:

env -u DURABLE_WORKFLOW_CLIENT_TOKEN php worker.php

In terminal two, reuse the runtime, namespace, and task queue but expose only the client role credential:

env -u DURABLE_WORKFLOW_WORKER_TOKEN php client.php
Expected result

The client prints a fresh php-quickstart-… workflow ID and "result":{"greeting":"hello, PHP"}. Stop the worker with Ctrl+C; the managed worker drains before returning.

What just became durable?

The runtime owns the workflow ID, run history, pending tasks, and terminal result. The PHP worker owns deterministic orchestration and activity code. If the process exits after an activity result is committed, another compatible worker can replay the history and continue without repeating that committed activity.

Next, configure role-specific clients or explore workflows and activities.