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

Venture Laravel Package

sassnowski/venture

Venture is a Laravel package for defining and running workflows as state machines. Model states and transitions, add guards and hooks, persist progress, and keep complex multi-step processes organized and testable.

View on GitHub
Deep Wiki
Context7

Getting Started

Install & migrate

composer require sassnowski/venture
php artisan vendor:publish --tag="venture-migrations"
php artisan migrate

Version note: v5.4.3 adds Laravel 13.x support. If you're on Laravel 13, pin ^5.4.3 (or later) — earlier 5.4.x releases will fail dependency resolution on the framework constraint. On Laravel 11/12 you can stay on any recent 5.4 patch.

Migrations create the persistence layer the package relies on: a workflows table for the workflow instance and its state, plus tables for the individual workflow jobs and their dependency edges. Nothing runs until a queue worker is consuming the connection your jobs land on, so confirm QUEUE_CONNECTION and get php artisan queue:work (or Horizon) running before you debug anything.

Where to look first

  1. The README/docs folder — the package is documentation-driven and the examples map almost 1:1 onto real use.
  2. src/Workflow.php and src/WorkflowDefinition.php — this is the fluent builder you'll spend all your time in (define(), addJob(), addDependency(), start()).
  3. src/Models/WorkflowJob.php — the row that represents one step. When something is stuck, this is where you look.
  4. src/Events/ — the workflow lifecycle events, which are your hook point for logging, metrics, and notifications.

First use case

Take an existing job that does three sequential things and split it into three queued jobs with explicit dependencies. You get retries per step, visibility into which step failed, and the ability to parallelise the middle step later without rewriting anything:

use Sassnowski\Venture\Workflow;
use Sassnowski\Venture\WorkflowDefinition;

class PublishEpisodeWorkflow extends Workflow
{
    public function __construct(public int $episodeId) {}

    public function definition(): WorkflowDefinition
    {
        return $this->define('Publish episode')
            ->addJob(new TranscodeAudio($this->episodeId))
            ->addJob(new GenerateTranscript($this->episodeId), [TranscodeAudio::class])
            ->addJob(new UploadToCdn($this->episodeId), [TranscodeAudio::class])
            ->addJob(new PublishEpisode($this->episodeId), [GenerateTranscript::class, UploadToCdn::class]);
    }
}

Start it from a controller, listener, or command:

$workflow = PublishEpisodeWorkflow::start($episode->id);
// persist $workflow->id against your episode if you want to show progress later

The steps themselves are ordinary ShouldQueue jobs. Keep them small and idempotent; Venture only handles ordering, not the work.

Implementation Patterns

Class-based workflows over inline definitions. For anything you'll start more than once, extend Workflow and put the graph in definition(). The fluent facade-style definition (Workflow::define(...)->...->start()) is fine for one-off scripts and tinker sessions, but class-based workflows are testable, reusable, and nestable.

Fan-out / fan-in. The dependency array on addJob is the whole graph language. A job with two dependencies waits for both; a job listed as a dependency of two others runs once and unblocks both. Model your pipeline as a DAG and let the queue do the parallelism.

Conditional branches. Add a dependency with a condition closure rather than branching inside a job:

->addJob(new SendNewsletter($this->userId))
->addJob(new SendPushNotification($this->userId), [
    SendNewsletter::class => fn () => $this->userWantsPush(),
])

Conditions are evaluated when the upstream job resolves, so they read the current state, not the state at start() time. This is how you do "skip this step if the user opted out" without a job that immediately returns.

Nested workflows. A workflow can be added as a job inside another workflow. Use this to compose a reusable sub-pipeline (e.g. "render video", "notify customer") into several parent processes. The parent waits for the nested workflow as a unit, which is exactly the granularity you want most of the time.

Lifecycle hooks for side effects. catch(), onFinish(), onCancel(), and the start hook are where you do the "the whole thing is done / died" work: flip a status column, fire a broadcast, send the failure alert. Do not put this logic in the last job's handle() — the last job runs even when the workflow is later cancelled or when a sibling branch fails.

Piping events out. Register listeners for the workflow lifecycle events (WorkflowStarted, WorkflowFinished, per-job started/finished/failed) and forward them to your own structured log or metrics backend. This is the cheapest observability you'll get, and it's the difference between "the workflow is stuck" and "job 3 has been failing for 20 minutes".

Queue routing per step. Set $queue, $tries, $backoff, and $timeout on each step job as usual — Venture respects the job's own queue configuration. That lets you put slow media steps on a dedicated worker pool and keep the fast orchestration on the default queue.

Testing. Use the database queue driver and run the worker inside the test, asserting on the workflow model's status and on your side effects. For unit-level tests of the definition itself, assert on the graph shape (job classes and their dependencies) rather than executing anything. Check the README for the package's own fakes/assertions before hand-rolling this. Since 5.4.3 also supports Laravel 13, run your suite on the framework version you actually deploy; if you support both 12 and 13, add a CI matrix row rather than assuming the orchestration internals behave identically across framework minors.

Cancellation as a first-class operation. When a user deletes the thing the workflow is processing, call the workflow's cancel method. It stops scheduling new steps; it does not stop already-dispatched jobs (see gotchas).

Gotchas and Tips

definition() runs at start, not at build time. Don't query the database or do heavy work in it. It should only construct job instances and edges. Put the lookups inside the jobs.

Job payloads live in the database. Every step is a serialized job payload in workflow_jobs. Passing entire Eloquent models with eager-loaded graphs will bloat the table and can break on deserialization after schema changes. Pass IDs and re-fetch in handle(). Same rule applies to closures used in conditions: capture scalars, not the request object or a DB connection.

Retries and idempotency. A step that fails and retries re-executes handle() from the top. Any step that writes to an external system (uploads, emails, payments) needs a guard. Dependent jobs are only dispatched after a step succeeds, so a half-finished step is the classic source of "the workflow said done but the file is corrupt".

Cancel is not kill. Cancelling prevents downstream dispatch; in-flight and already-queued jobs still run. If cancellation must be immediate, have handle() check a status flag on the parent record and bail early.

Cycles are your problem. A dependency cycle means those steps simply never become ready — no error, no exception, just a workflow that sits in "in progress" forever. Keep the graph acyclic and, if the graph is dynamic, add a sanity test that asserts no cycle in the definitions you ship.

Queue connection mismatch. The most common "it's stuck" cause is that step jobs are dispatched to a queue or connection that has no worker. If you override $queue on a step job, update your supervisor/Horizon config in the same PR.

sync driver behaves differently. With QUEUE_CONNECTION=sync everything runs inline in the request, failure semantics and ordering look different, and long workflows will time out. Use the database driver locally even if production is Redis.

Table growth. workflows and workflow_jobs accumulate forever. Schedule a cleanup that deletes finished/cancelled workflows older than your retention window (cascade the job rows or delete in batches). Do it before the table is 10M rows, not after.

Debugging a stuck workflow. Query workflow_jobs for the workflow ID: the rows without finished_at are your candidates, and the ones without failed_at that are also not dispatched point at a dependency that never resolved. Cross-check failed_jobs and your worker logs. php artisan queue:failed plus the workflow's job list usually identifies the culprit within a minute.

Failed jobs inside a workflow are retried by the queue, not by Venture. If you rely on failed() callbacks on step jobs, remember they fire per-job on final failure, whereas the workflow-level catch()/failure hook fires once for the workflow. Wire the user-facing alert to the workflow hook, and the per-job alert to the job.

Extension points worth knowing. The lifecycle events, the catch/onFinish/onCancel hooks, and the nested-workflow mechanism are the three sanctioned seams. If you find yourself wanting to subclass the orchestration job or poke at the dependency rows directly, check whether an event listener gets you there first — it usually does, and it survives package upgrades.

Version constraints and upgrade cadence. The package is on the 5.x line and patch releases have recently been framework-compatibility bumps (5.4.3 = Laravel 13 support, no API changes). Constrain to ^5.4 in composer.json so compatibility patches flow in automatically, and read the changelog before crossing a minor. If you're upgrading your app to a new Laravel major, check Venture's releases first — the framework constraint is what usually blocks composer update, and the fix is a patch bump rather than a code change on your side.

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.
frankyso/ipaymu
3mdikram/worldmoneywords
snowsoft/config
itmosoft/laravel-subscriptions
larva/laravel-ai-qwen
diephp/sequences
users-au/socialite-provider
twidpay/twid-logger
mrnewport/laravel-plaid
mrnewport/laravel-repo
mrnewport/laravel-docsign
mrnewport/laravel-stow
mrnewport/laravel-flow
mrnewport/laravel-priceable
mrnewport/laravel-nestedterms
fredmeng/laravel-apidoc-generator
itstructure/laravel-multilingual-tools
pdustdar/elasticsearch
markupai/php-sdk
rlacerda83/lumem-microservice-shipment