Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Sdk Laravel Package

temporal/sdk

PHP SDK for authoring Temporal Workflows and Activities, enabling scalable, durable async business logic. Clients require ext-grpc; Workers require RoadRunner, with ext-protobuf recommended for production performance. Install via Composer.

View on GitHub
Deep Wiki
Context7

Getting Started

  1. Install: composer require temporal/sdk

  2. Match extensions to role — the SDK splits into two halves with different requirements:

    Component Needs Recommend
    Client (start/signal/query) ext-grpc ext-protobuf
    Worker (executes code) RoadRunner binary ext-protobuf

    ./vendor/bin/rr get downloads RoadRunner. A client-only app (a Laravel controller dispatching workflows) does not need RoadRunner; a worker app does not need ext-grpc.

  3. Run a local server: temporal server start-dev --log-level error gives you a full server + Web UI + SQLite on 127.0.0.1:7233. No Docker required.

  4. Where to look first, in order: the samples-php repo (working end-to-end code beats docs), then docs.temporal.io/develop/php, then php.temporal.io for the API surface. The SDK's own README is a setup guide, not a tutorial — don't expect to learn the programming model from it.

  5. First real use case: replace a cron job or queued chain that has become un-debuggable — e.g. "reserve stock → charge card → send receipt" with retries, sleeps, and a compensating refund. That exercises the three things Temporal is actually for: durable retries, durable timers, and visible execution history.

Minimal shape of that first workflow:

#[WorkflowInterface]
interface OrderWorkflow
{
    #[WorkflowMethod]
    public function run(string $orderId): string;
}

Implementation Patterns

Workflow/Activity split is the whole design. Workflows must be deterministic; anything touching the network, disk, clock, or randomness belongs in an Activity. A practical rule: if you'd mock it in a unit test, it's an Activity.

Activity stubs, not Activity classes, inside workflows. Workflows depend on the interface; the worker wires the implementation:

$charge = Workflow::newActivityStub(
    PaymentActivity::class,
    ActivityOptions::new()
        ->withStartToCloseTimeout('30 seconds')
        ->withRetryPolicy(RetryPolicy::new()
            ->withMaximumAttempts(5)
            ->withNonRetryableErrorTypes([CardDeclined::class])),
);

$receipt = yield $charge->charge($orderId, $amount);

Set startToCloseTimeout on every activity stub — it is the one option the SDK cannot sensibly default, and a missing timeout surfaces late as a stuck workflow rather than an error.

Orchestration with promises. PHP workflows are generators; yield awaits. Fan out and join with Promise::all / Promise::any rather than sequential awaits when the calls are independent. In v2.19+, FeatureFlags::$settleAwaitOnFirstSettledCondition = true makes multi-condition await()/awaitWithTimeout() unblock on the first settled promise (rejection propagates) instead of waiting out the timer.

Signals, queries, updates for external input. Long-running workflows typically yield Workflow::await(...) on a signal flag or an activity promise, with Workflow::awaitWithTimeout() for the "wait up to N days, then escalate" shape. Updates (updateWithStart) need server-side flags on dev clusters — see the README's --dynamic-config-value list.

Client side in a framework app. Keep a single long-lived WorkflowClient (bound in a service provider / container singleton) — it wraps a gRPC connection, so constructing one per request is wasteful. Use start() when you need the result inline, startAsync() + getResult() when you don't, signalWithStart() when the workflow may or may not already exist, and Schedules for true cron replacements.

Worker as a separate process. Boot via WorkerFactory + RoadRunner, register workflow implementation types and activity implementations, and point it at the same task queue the client uses:

$factory = WorkerFactory::create();
$worker  = $factory->newWorker('orders', WorkerOptions::new()->withMaxConcurrentActivityTaskPollers(4));
$worker->registerWorkflowImplementationTypes([OrderWorkflowImpl::class]);
$worker->registerActivityImplementations([$container->get(PaymentActivity::class)]);
$factory->run();

Keep the worker in its own deployable, scaled independently of the web app. This is the main architectural change when adopting the package.

Testing is a first-class workflow, use it. TestWorkflowEnvironment + WorkflowStub lets you mock activities and run the whole workflow in-process without a server; the README also points at the Replay API for CI, which re-executes stored histories against new code to catch nondeterminism before you deploy. Wire both into CI — replay catching a determinism break is worth more than any unit test.

For Spiral users, the framework integration (spiral/temporal-builder) removes most of the worker boilerplate. For Laravel/Symfony there's no official bridge as of this writing; you'll write a thin service provider and an rr config file yourself.

Gotchas and Tips

  • You cannot var_dump, echo, or print_r in a worker. STDOUT is swallowed by RoadRunner. Install Buggregator with trap()/dump() — Trap even patches var-dumper to render protobuf messages readably, which matters constantly here. Teams that skip this lose days to blind debugging.
  • Nondeterminism is a silent, delayed failure. time(), rand(), date(), file_get_contents(), new DateTime(), and $this-mutation ordering inside a workflow can all break replay weeks later. Use Workflow::now(), Workflow::sideEffect(), and move everything else into activities. Turn on the Replay API tests early, not after the first incident.
  • yield is not optional and is easy to forget. A missing yield returns the promise object itself; the workflow completes "successfully" with the wrong value and only replay/tests catch it. Code review for $x = $activity->foo() without yield in workflow classes.
  • Version workflows before changing them. Workflow::getVersion() (patching) is the supported way to alter a running workflow's code path; without it, in-flight executions fail or diverge on the next worker deploy. Note that getVersion calls themselves become part of the history and must be preserved.
  • Feature flags are opt-in and change worker behavior globally. Since v2.11.0 many correctness fixes ship behind Temporal\Worker\FeatureFlags statics (e.g. $settleAwaitOnFirstSettledCondition in v2.19). The README explicitly recommends disabling deprecated behavior — but do it deliberately, in a release where you've replayed histories, since flipping a flag changes the commands emitted.
  • Payload serialization bites on typed DTOs. The default converter is JSON-based; DTOs need public/serializable properties and a working constructor, or arguments arrive as empty objects. Large payloads hit the 2MB gRPC limit — use the external storage/claim-check pattern rather than bumping limits.
  • Retry policies default to retry forever with backoff. A bug in an activity will loop indefinitely and quietly cost money. Always cap maximumAttempts or maximumInterval, and mark business-logic failures non-retryable via withNonRetryableErrorTypes or by throwing ApplicationFailure.
  • Task queue and namespace mismatches produce "nothing happens", not an exception. The workflow starts, sits in Running, and no worker picks it up. First debugging step for any stalled execution: confirm client and worker agree on both task queue and namespace.
  • Workers cache code in memory — restart them on deploy. RoadRunner keeps the PHP process warm; stale code + new history is the classic post-deploy determinism error. Also watch for accumulated static state and long-lived DB connections in singleton activities.
  • Activity heartbeats for anything long. Without Activity::heartbeat() plus a heartbeatTimeout, a crashed worker leaves the activity to time out only at startToCloseTimeout — potentially an hour of dead time instead of seconds.
  • The admin/cloud surfaces are separate clients. OperatorClient::create(...) for namespace/operator work and CloudClient::createSSL(...)->withAuthKey(...) (v2.19+) for Temporal Cloud; they share connection handling with WorkflowClient but are not the same object. withApiVersion() pins the required temporal-cloud-api-version header, and a version already present on the call context wins over it.
  • Version pinning: the roadrunner-api-dto constraint moves with releases (v2.19 requires ^1.15.0); a stale lockfile alongside a newer temporal/sdk is a common source of confusing gRPC stub mismatches. Bump both together.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
htinlynn/genius-helper
maco-studios/hephaestus
elmogy/larafast
xeviant/laravel-paystack
gorilladash/laravel-website-sdk
martialbe/flysystem-oss
chondal/3312client
jeroen-g/flickr
opscale-co/nova-genbi
itsjustvita/laravel-retention
tequilarapido/trackit
gregsphotography/site-builder-filament
cabaoctavio/laravel-query-log
gregsphotography/site-builder-components
io-digital/clickatell
snowsoft/laravel-admin
oxygen/auth
relicsoftware/barakah-payment-gateway
tobya/work-with-composer
equipc/laravel-email-validator