Venture for Laravel
website·Indexed 31 days ago
https://laravel-venture.comA package that extends Laravel's queue system to build complex workflows from regular Laravel jobs. It provides tools for defining job dependencies, managing workflow states, nesting workflows, and testing definitions with dedicated assertions like assertJobExists and assertWorkflowExists.
What's inside Venture
- Venture is a package built on top of Laravel's queue system that allows developers to create complex workflows using regular Laravel jobs. It manages the execution order by parallelizing jobs without interdependencies and ensuring jobs only start after their defined dependencies are resolved. Key features include defining job dependencies, conditional job addition, and manual gates.
Overview of Venture workflow testing
Venture provides a set of first-class test helpers designed to allow developers to reason about workflows without the need to execute them fully or dispatch numerous jobs. The two primary testing patterns for workflows are asserting that a workflow was started and testing the specific definition of a workflow.Understand how job failures affect workflow execution
In a workflow, if a job fails to execute successfully, only the jobs in that specific subtree (those with a direct or transitive dependency on the failed job) are prevented from running. Jobs that do not depend on the failed job will continue to execute normally, ensuring the workflow is robust and not 'all-or-nothing'.Embed a nested workflow in a workflow definition
To reuse a subset of jobs across different workflows, you can embed one workflow inside another using theaddWorkflowmethod on theWorkflowDefinitioninstance. The outer workflow treats the nested workflow as if its individual jobs were added directly.<?php namespace App\Workflows; use Sassnowski\Venture\AbstractWorkflow; use Sassnowski\Venture\WorkflowDefinition; class PublishPodcastWorkflow extends AbstractWorkflow { public function __construct(private Podcast $podcast) { } public function definition(): WorkflowDefinition { return $this->define('Publish Podcast') ->addWorkflow(new OptimizePodcastWorkflow($this->podcast)); } }Update WorkflowDefinition and Workflow::define for Venture 4.0
TheWorkflowDefinitionconstructor and theWorkflow::definemethod now require an instance ofAbstractWorkflowas the first parameter. In a workflow class, you should now pass$thisto thedefinemethod, or use the conveniencedefinemethod available on theAbstractWorkflowclass.class PublishPodcastWorkflow implements AbstractWorkflow { public function definition(): WorkflowDefinition { - return Workflow::define('Publish podcast'); + return Workflow::define($this, 'Publish podcast'); + // OR use the convenience method: + // return $this->define('Publish podcast'); } }Add jobs and define dependencies in a workflow
Jobs are added to a workflow using theaddJobmethod on theWorkflowDefinitioninstance. To define dependencies, pass an array of class names as the second parameter toaddJob. A job will only execute once all its specified dependencies have successfully finished. If no dependencies are provided, jobs will start in parallel.$this->define('Publish new podcast') ->addJob(new ProcessPodcast($this->podcast)) ->addJob(new OptimizePodcast($this->podcast)) ->addJob(new PublishPodcastOnTransistorFM($this->podcast), [ ProcessPodcast::class, OptimizePodcast::class ]);Hook into Venture events using PluginContext
Within a plugin'sinstallmethod, use thePluginContextto register event listeners. You can use closures or Laravel-compatible event handlers (class strings, arrays, or string syntax). Handlers registered via class strings are resolved from Laravel's container, allowing for constructor dependency injection. Multiple handlers can be registered for the same event.public function install(PluginContext $context): void { // Using a closure $context->onWorkflowCreating(function (WorkflowCreating $event) { // logic here }); // Using a class string (resolved via container) $context->onWorkflowCreating(MyEventListener::class); // Using array syntax $context->onWorkflowCreating([MyEventListener::class, 'execute']); // Using string syntax $context->onWorkflowCreating('App\Listeners\MyEventListener@execute'); }Upgrade Laravel Venture to version 3.0
Upgrading to Venture 3.0 requires PHP 8.0. TheaddJobWithDelaymethod has been removed fromWorkflowDefinition; use theaddJobmethod with PHP 8 named parameters instead.// Old way (removed) Workflow::define('Workflow name') ->addJobWithDelay(new Job(), now()->addMinute()); // New way (PHP 8 named parameters) Workflow::define('Workflow name') ->addJob(new Job(), delay: now()->addMinute());Manage plugin dependencies and custom setup
Because plugins are resolved via Laravel's container, you can typehint dependencies in the plugin's constructor. Additionally, the
installmethod can be used for general Venture configuration, such as defining custom workflow or job models usingVenture::useWorkflowModelandVenture::useWorkflowJobModel.Caveat: Plugins are installed during the
bootphase. Do not performregisterphase tasks (like container bindings) inside a plugin'sinstallmethod.<?php use App\SomeService; use App\Models\MyWorkflowModel; use Sassnowski\Venture\Plugin\Plugin; use Sassnowski\Venture\Plugin\PluginContext; class MyPlugin implements Plugin { public function __construct(private SomeService $service) {} public function install(PluginContext $context): void { Venture::useWorkflowModel(MyWorkflowModel::class); // Other setup... } }Use closures as workflow jobs
Venture allows adding closures as jobs. Because closures lack a class name, you must provide an explicit ID using theidparameter inaddJob. Dependencies can be typehinted in the closure and will be resolved from Laravel's container. To access the job instance itself, include a parameter named$jobof typeWorkflowableJob.use Sassnowski\Venture\WorkflowableJob; $this->define('My Workflow') ->addJob(function (TruthGenerator $truths, WorkflowableJob $job) { Log::info($truths->getHotTake()); Log::info($job->workflow()); }, id: 'log-truth');Configure dependencies for nested workflows and jobs
Nested workflows and individual jobs can have dependencies on each other.
- Workflow depending on a job: When a nested workflow depends on a job, all root nodes (starting jobs) of that nested workflow will depend on the specified job.
- Job depending on a workflow: A job depends on a nested workflow by waiting for all of the nested workflow's 'final' jobs (jobs with no further dependents) to complete.
- Workflow depending on another workflow: All starting jobs of the dependent workflow will depend on all final jobs of the prerequisite workflow.
<?php namespace App\Workflows; use Sassnowski\Venture\AbstractWorkflow; use Sassnowski\Venture\WorkflowDefinition; class PodcastWorkflow extends AbstractWorkflow { public function definition(): WorkflowDefinition { return $this->define('Podcast Workflow') // 1. Job that others depend on ->addJob(new ProcessPodcast($this->podcast)) // 2. Nested workflow depending on a job ->addWorkflow(new EncodePodcastWorkflow($this->podcast), [ ProcessPodcast::class, ]) // 3. Nested workflow depending on another nested workflow ->addWorkflow(new PublishPodcastWorkflow($this->podcast), [ EncodePodcastWorkflow::class, ]) // 4. Job depending on a nested workflow ->addJob(new NotifySubscribers($this->podcast), [ ReleasePodcast::class, ]); } }Implement custom workflow state transitions
Venture allows developers to override the default state transitioning behavior of workflows by implementing theWorkflowStateinterface. This is an 'escape hatch' for advanced extensions where the developer becomes responsible for maintaining consistent states.interface WorkflowState { public function markJobAsFinished(WorkflowableJob $job): void; public function markJobAsFailed(WorkflowableJob $job, Throwable $exception): void; public function allJobsHaveFinished(): bool; public function isFinished(): bool; public function markAsFinished(): void; public function isCancelled(): bool; public function markAsCancelled(): void; public function remainingJobs(): int; public function hasRan(): bool; }