vanta/temporal-bundle
Vanta Temporal Bundle integrates Temporal workflow orchestration into Laravel, with configurable workers, workflow and activity registration, and Artisan commands for running durable, fault-tolerant background processes and long-running tasks in a familiar package structure.
temporal/sdk (and, for real concurrency, the RoadRunner worker). Require the bundle with Composer and let Flex register it, or add it manually to config/bundles.php.php bin/console config:dump-reference <bundle_alias>. Read src/DependencyInjection/Configuration.php in the vendor tree — that file is the authoritative list of options (address, namespace, task queues, data converter, worker settings). Do not copy config from the README blindly; check it against the installed version.temporal server start-dev) or the official docker-compose, which gives you localhost:7233 plus a UI on :8233. Point the bundle at it via env (TEMPORAL_ADDRESS / the bundle's address option) rather than hardcoding.workflows:/activities: config list), and start a worker with the bundle's console command.temporal workflow show --id <id>. That loop — start, execute, inspect — is the whole day-to-day workflow.Where to look first, in order: README → Configuration.php → the worker command class → the compiler pass that wires workflow/activity services. Those four files tell you 90% of how the bundle behaves.
Separate orchestration from effects. Workflows are deterministic state machines; activities are ordinary Symfony services. Keep repositories, HTTP clients, mailers, and clocks out of workflow constructors and put them in activities. The bundle autowires both, so this is a discipline problem, not a technical one.
Register by tag, not by hand. Most bundles of this shape let you tag services (e.g. temporal.workflow / temporal.activity) or configure them in YAML. Tagging is preferable: it keeps registration next to the class and survives refactors. Group workflows and activities into task queues by domain (billing, notifications, ingestion) and run one worker process per queue, so a traffic spike in one domain doesn't starve another.
Trigger async, poll or signal for status. Controllers should call startWorkflow() with explicit WorkflowOptions — task queue, workflow execution timeout, retry policy, and a search attribute or memo carrying your entity ID — then return the workflow ID immediately. Use signals to push updates into a running workflow and queries to expose read-only state (progress, current step) to your UI. Never block a request on a long workflow result.
Activities are where Symfony integration happens. Inject the entity manager, the mailer, the HTTP client, and wrap each activity method around a single unit of work. Return primitives or DTOs — never Doctrine entities.
Use the Symfony serializer as a data converter if you have non-trivial DTOs. A custom DataConverterInterface bound to the bundle's converter service means payloads match what your HTTP layer already produces, and the Temporal UI becomes readable. If you don't need that, the default JSON converter with plain DTOs and public properties is less work.
Test in two layers. Activity services are plain PHP — test them like any other service. Workflows should be tested against WorkflowEnvironment::createTimeSkipping() (or a local dev server in CI), so timers and retries don't cost real wall-clock time. Run the worker in-process during tests with a test task queue to avoid collisions.
Version workflow code deliberately. Once a workflow type is running in production, use Workflow::getVersion() (patching) before changing the sequence of commands, or route new executions to a new task queue with the new code.
time(), rand(), sleep(), new \DateTime(), file_get_contents(), and static caches inside workflow methods all break replay. Use Workflow::now(), Workflow::timer(), and Workflow::sideEffect() for anything non-deterministic. If a worker logs "workflow task timeout" or nondeterminism errors, suspect this first, not the server.bin/console worker command may be fine for a handful of workflows but will bottleneck under load. Run workers under supervisor/systemd with restart-on-failure, and restart them on deploy.ApplicationFailure; otherwise users see long stalls before an obvious error surfaces.vanta_temporal:), and some options only take effect through a compiler pass — clearing the cache is mandatory after changing them. If a workflow isn't registered, debug:container --tag=<tag> is faster than reading YAML.temporal workflow list/describe/show, temporal workflow stack for stuck executions, plus the Web UI's event history. Event history tells you exactly which activity or timer the workflow is parked on; worker logs tell you why it failed.maxConcurrentActivityTaskPollers or similar.composer.json, read the source before leaning on undocumented behavior, and assume you may need to fork for a fix. The underlying temporal/sdk is the stable part; treat the bundle as thin glue you could replace with a few service definitions if it goes stale.How can I help you explore Laravel packages today?