Workers
A worker is a long-running PHP process that advertises type names, polls one task queue, replays workflows, runs activities, and heartbeats its registration.
Register every handler before polling
Choose one registration style for each handler. Attribute discovery is the preferred class-oriented path; direct registration is useful when an application already produces callables.
register() validates all supplied services before the worker contacts the
runtime. Passing an un-attributed class raises InvalidWorkerDefinition before
registration or polling begins. Add a Durable Workflow handler attribute, or
use the direct callable API instead.
Add the other handler types to the same worker before starting its loop:
$worker = new Worker(
client: $client,
taskQueue: 'orders',
buildId: getenv('APP_RELEASE') ?: null,
);
$worker
->declareSignal('orders.process', 'approve')
->registerQuery('orders.process', 'status', $statusQuery)
->registerUpdate('orders.process', 'change-address', $addressUpdate);
$worker->run();
Workflow and activity type names are protocol identities. Keep them stable even when PHP class names or namespaces change. Duplicate registrations fail during bootstrap rather than becoming order-dependent.
One task queue is one routing boundary
Scale a queue by starting more workers with the same registered types and a compatible build ID. Split queues when work needs different dependencies, resource limits, deployment cadence, or trust boundaries.
Do not send a workflow to a queue that has no compatible workflow handler. Inspect listWorkers() and describeTaskQueue() before assuming a deployment received traffic.
Let the managed loop own the protocol
Worker::run() registers, negotiates heartbeat cadence, polls workflow/activity/query tasks, renews workflow-task leases, and reports terminal outcomes. Typed transient poll pressure is retried with capped backoff while registration heartbeats and shutdown checks remain responsive.
Use tick() only when a framework supervisor must own the outer loop:
while (!$shutdownRequested) {
$worker->tick(pollTimeoutSeconds: 1);
}
$worker->requestShutdown();
The lower-level poll*TaskResponse() APIs are for adapters that intentionally own lease, attempt, completion, and failure semantics. Application workers should prefer the managed loop.
Supervise the process
Run one process per container or a bounded number under systemd, Supervisor, Kubernetes, Laravel process management, or Symfony Process. A production supervisor should:
- restart unexpected exits with backoff;
- deliver
SIGTERMand allow a drain window; - report the worker build ID and task queue;
- cap memory and recycle processes deliberately;
- keep client and worker credentials separate.
Workers need a real process lifecycle. Start them through your platform's process supervisor, not a controller, queue callback, or request shutdown hook.
Observe worker health
Use Server or Cloud visibility to verify registration freshness, build compatibility, and queue depth. Treat “the process exists” and “the worker is admitted and polling” as different health checks.