Laravel Venture
website·Indexed 30 days ago
https://laravel-venture.netlify.app/A workflow management package for Laravel that allows developers to define, nest, and track workflows. It includes features for handling duplicate jobs, managing failures, customizing internal models and migrations, and a first-class set of test helpers for asserting workflow execution.
What's inside Venture
- Venture provides a set of first-class test helpers designed to allow developers to reason about workflows without the need to actually start them or dispatch the associated sequence of jobs. The two primary testing scenarios 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 Venture workflow, if a job fails, all subsequent 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'.Publish Venture database migrations for customization
To alter the migrations provided by the Venture package, you must first publish them to your application'sdatabase/migrationsfolder. This allows you to modify the schema, though you should avoid changing default columns as it may cause the package to malfunction.php artisan vendor:publish --provider="Sassnowski\Venture\VentureServiceProvider" --tag=migrationsFake and assert workflow execution in tests
Use theWorkflowfacade'sfake()method to prevent workflows from actually executing during a test. Once faked, you can useassertStartedto verify a specific workflow class was triggered, orassertNotStartedto verify it was not. To perform more granular assertions on the workflow instance, pass a callback as the second argument to the assertion method; the test will fail if the callback returnsfalse.<?php declare(strict_types=1); namespace Testseature; use Tests//TestCase; use App//Models//Podcast; use App//Workflows//AnotherWorkflow; use Sassnowski//Venture//Facades//Workflow; use App//Workflows//PublishPodcastWorkflow; class ExampleTest extends TestCase { /** @test */ public function testPodcastPublishing(): void { // Prevent workflows from actually running Workflow::fake(); // Action that triggers workflows // Publish podcast... // Assert that a specific workflow was started Workflow::assertStarted(PublishPodcastWorkflow::class); // Assert that a workflow was not started Workflow::assertNotStarted(AnotherWorkflow::class); // Assert with a custom callback for specific instance data Workflow::assertStarted( PublishPodcastWorkflow::class, function (PublishPodcastWorkflow $workflow) use ($podcast) { return $workflow->podcast->is($podcast); } ); } }Upgrade Venture to 3.0 from 2.x
When migrating to Venture 3.0, note the following changes:
- PHP Version: The minimum required PHP version is now 8.0.
addJobWithDelayRemoval: TheaddJobWithDelaymethod inWorkflowDefinitionhas been removed. Use theaddJobmethod with PHP 8 named parameters instead.- Nested Workflow Job IDs: Jobs from nested workflows (added via
addWorkflow) are now internally prefixed with the ID of the parent workflow. This affects tests usinghasJob()assertions.
// Old way (Removed) Workflow::define('Workflow name')->addJobWithDelay(new Job(), now()->addMinute()); // New way (Venture 3.0) Workflow::define('Workflow name')->addJob(new Job(), delay: now()->addMinute());Modify a workflow before it is saved using the beforeCreate hook
To apply transformations to a workflow before it is saved to the database for the first time, implement the
beforeCreatemethod within your workflow class (which should extendAbstractWorkflow).The
beforeCreatemethod receives an instance ofSassnowski\Venture\Models\Workflow, which is a standard Eloquent model. Because this hook executes before the initial persistence, the workflow model will not yet have anidassigned.<?php declare(strict_types=1); use Sassnowski\Venture\Models\Workflow; use Sassnowski\Venture\AbstractWorkflow; use Sassnowski\Venture\WorkflowDefinition; class PublishPodcastWorkflow extends AbstractWorkflow { private Podcast $podcast; public function __construct(Podcast $podcast) { $this->podcast = $podcast; } public function definition(): WorkflowDefinition { // ... } public function beforeCreate(Workflow $workflow): void { // Perform some transformation to the workflow before it gets saved... $workflow->user_id = $this->podcast->user_id; } }Add jobs and define dependencies in a workflow
Jobs are added to a workflow using theaddJobmethod. To specify that a job depends on others, pass an array of the dependency job class names as the second argument. The job will only execute once all listed dependencies have finished. Venture automatically handles transitive dependencies (dependencies of dependencies).Workflow::define('Publish new podcast') ->addJob(new ProcessPodcast($podcast)) ->addJob(new OptimizePodcast($podcast)) ->addJob(new PublishPodcastOnTransistorFM($podcast), [ ProcessPodcast::class, OptimizePodcast::class ]);Manage duplicate jobs in nested workflows
Venture automatically prefixes job IDs within nested workflows to prevent naming conflicts with the parent workflow. This allows a nested workflow to contain a job class that also exists in the parent workflow without requiring explicit IDs for the nested instance.
Caveat: You should always depend on the nested workflow itself rather than a specific job inside that workflow to maintain encapsulation and avoid altering the workflow structure.
// Nested Workflow class FlacPodcastWorkflow extends AbstractWorkflow { public function definition(): WorkflowDefinition { return Workflow::define('Flac Workflow') ->addJob(new EncodePodcast('flac', $this->podcast)) ->addJob( new NotifyAudiophileMailingList($this->podcast), dependencies: [EncodePodcast::class], ); } } // Parent Workflow Workflow::define('Publish podcast') ->addJob(new ProcessPodcast($this->podcast)) ->addJob( new EncodePodcast('mp3', $this->podcast), dependencies: [ProcessPodcast::class], id: 'encode-mp3' ) ->addWorkflow( new FlacPodcastWorkflow($this->podcast), dependencies: [ProcessPodcast::class], );Define a workflow with job dependencies in Laravel Venture
To create a workflow in Laravel Venture, extend theAbstractWorkflowclass and implement thedefinition()method. UseWorkflow::define()to name the workflow andaddJob()to attach jobs. You can specify job dependencies by passing an array of class names as the second argument toaddJob(); the job will only execute once all listed dependencies have successfully finished. Jobs without interdependencies are automatically parallelized.class PublishNewPodcastWorkflow extends AbstractWorkflow { private Podcast $podcast; public function __construct(Podcast $podcast) { $this->podcast = $podcast; } public function definition(): WorkflowDefinition { Workflow::define('Publish new podcast') ->addJob(new ProcessPodcast($this->podcast)) ->addJob(new OptimizePodcast($this->podcast)) ->addJob(new ReleaseOnTransistorFM($this->podcast), [ // This job runs only after ProcessPodcast and OptimizePodcast finish ProcessPodcast::class, OptimizePodcast::class ]) ->addJob(new ReleaseOnApplePodcasts($this->podcast), [ ProcessPodcast::class, OptimizePodcast::class ]) ->addJob(new CreateAudioTranscription($this->podcast), [ ProcessPodcast::class, ]) ->addJob(new TranslateAudioTranscription($this->podcast), [ CreateAudioTranscription::class, ]) ->addJob(new NotifySubscribers($this->podcast), [ ReleaseOnTransistorFM::class, ReleaseOnApplePodcasts::class, ]) ->addJob(new SendTweetAboutNewPodcast($this->podcast), [ TranslateAudioTranscription::class, ReleaseOnTransistorFM::class, ReleaseOnApplePodcasts::class, ]); } }Add multiple instances of the same job to a workflow
When adding multiple jobs of the same class to a workflow, you must provide a uniqueidfor each instance using theidparameter in theaddJobmethod. If no ID is provided, Venture defaults to using the Fully Qualified Class Name (FQCN) of the job class. If two jobs share the same ID within a single workflow definition, Venture will throw aDuplicateJobException.Workflow::define('Publish podcast') ->addJob(new ProcessPodcast($this->podcast)) ->addJob( new EncodePodcast('mp3', $this->podcast), dependencies: [ProcessPodcast::class], id: 'encode-mp3' ) ->addJob( new EncodePodcast('wav', $this->podcast), dependencies: [ProcessPodcast::class], id: 'encode-wav' );Delay the execution of a workflow job
To delay a job, use theaddJobWithDelaymethod. The job class must use theIlluminate\Bus\Queueabletrait to support this functionality.$workflow = Workflow::new('Publish new podcast') ->addJobWithDelay(new ProcessPodcast($podcast), now()->addDays(2));Run Venture workflow jobs synchronously
Venture requires all workflow jobs to implement theShouldQueueinterface because it relies on Laravel's queue system events. To run a job synchronously while maintaining this requirement, you must set the job's connection tosync. This requires the job to use theIlluminate\Bus\Queueabletrait. You can achieve this either by setting$this->connection = 'sync'in the constructor or by callingonConnection('sync')when adding the job to the workflow definition.// Option 1: In the job constructor namespace App\Jobs; use Illuminate\Bus\Queueable; use Sassnowski\Venture\WorkflowStep; use Illuminate\Contracts\Queue\ShouldQueue; class MyJob implements ShouldQueue { use WorkflowStep, Queueable; public function __construct() { $this->connection = 'sync'; } } // Option 2: When defining the workflow Workflow::define('My workflow') ->addJob((new MyJob())->onConnection('sync'));