ibrostudio / laravel-tasks
Tasks manager for Laravel
Requires
- php: ^8.4
- ibrostudio/laravel-data-objects: ^1.9
- illuminate/contracts: ^10.0||^11.0||^12.0
- lorisleiva/laravel-actions: ^2.9
- spatie/laravel-activitylog: ^4.10
- spatie/laravel-package-tools: ^1.16
- tightenco/parental: ^1.4
Requires (Dev)
- larastan/larastan: ^2.9||^3.0
- laravel/boost: ^1.0
- laravel/pint: ^1.14
- nunomaduro/collision: ^8.1.1||^7.10.0
- orchestra/testbench: ^10.0.0||^9.0.0||^8.22.0
- pestphp/pest: ^3.0
- pestphp/pest-plugin-arch: ^3.0
- pestphp/pest-plugin-laravel: ^3.0
- phpstan/extension-installer: ^1.3||^2.0
- phpstan/phpstan-deprecation-rules: ^1.1||^2.0
- phpstan/phpstan-phpunit: ^1.3||^2.0
- tightenco/duster: ^3.2
README
A comprehensive Laravel package for managing pipelined processes and their tasks with state management, logging, and async execution capabilities.
Features
- Pipeline-based Process Execution: Execute complex workflows using Laravel's Pipeline pattern
- State Management: Track process and task states (PENDING, PROCESSING, COMPLETED, ABORTED, WAITING)
- Task Orchestration: Chain multiple tasks within processes with automatic state transitions
- Pause & Resume: Pause processes and resume them via signed URLs
- Nested Processes: Execute processes within other processes for complex workflows
- Async Execution: Dispatch processes to queues for background processing
- Comprehensive Logging: Built-in activity logging with batch support
- Polymorphic Relations: Associate processes with any Eloquent model
- Payload Management: Type-safe payload handling with DTOs
- Exception Handling: Graceful error handling with task-specific exceptions
Installation
Install the package via Composer:
composer require ibrostudio/laravel-tasks
Run the installation command:
php artisan tasks:install
This command will:
- Publish the configuration file
- Publish and run the migrations
Configuration
The package publishes a configuration file to config/tasks.php
:
<?php return [ 'log_processes' => true, // Enable/disable process logging 'queue' => 'processes', // Queue name for async process execution ];
Database Schema
The package creates two main tables:
processes
table
id
: Primary keytype
: Process class namepayload
: JSON payload datastate
: Current process stateprocessable_id/processable_type
: Polymorphic relationprocessable_dto
: JSON DTO dataparent_process_id
: For nested processeslog_batch_uuid
: Activity log batch UUIDcreated_at/updated_at
: Timestamps
processes_tasks
table
id
: Primary keyprocess_id
: Foreign key to processestype
: Task class nameas_process_id
: For tasks that run as sub-processesstate
: Current task stateprocessable_id/processable_type
: Polymorphic relationprocessable_dto
: JSON DTO data
Basic Usage
Creating a Process
Create a process class by extending the base Process
model:
<?php namespace App\Processes; use IBroStudio\Tasks\Models\Process; use IBroStudio\Tasks\Dto\ProcessConfigDto; use App\Tasks\ValidateDataTask; use App\Tasks\ProcessDataTask; use App\Tasks\SendNotificationTask; class DataProcessingProcess extends Process { protected function getConfig(array $properties = []): ProcessConfigDto { return ProcessConfigDto::from([ 'tasks' => [ ValidateDataTask::class, ProcessDataTask::class, SendNotificationTask::class, ], 'use_logs' => true, 'log_name' => 'data-processing', ...$properties, ]); } }
Creating Tasks
Create task classes by extending the base Task
model:
<?php namespace App\Tasks; use IBroStudio\Tasks\Models\Task; use IBroStudio\Tasks\Contracts\PayloadContract; class ValidateDataTask extends Task { /** * @param MyCustomPaylodDto $payload */ protected function execute(PayloadContract $payload): PayloadContract|array { // Perform task logic // Return updated payload return $payload->updateDto(['validated' => true]); } }
Creating Custom Payloads
Create type-safe payload DTOs:
<?php namespace App\Dto; use IBroStudio\Tasks\Dto\DefaultProcessPayloadDto; class DataProcessingPayloadDto extends DefaultProcessPayloadDto { public function __construct( public array $data, public bool $validated = false, public bool $processed = false, public ?string $notification_sent = null, ) {} }
Payloads DTOs extend Spatie Laravel Data.
Executing Processes
Synchronous Execution
use App\Processes\DataProcessingProcess; use App\Dto\DataProcessingPayloadDto; // Create and execute a process $process = DataProcessingProcess::->create([ 'payload' => DataProcessingPayloadDto::from([ 'data' => ['user_id' => 123, 'required_field' => 'value'] ]) ]); $result = $process->handle(); // Check the result if ($result->state === ProcessStatesEnum::COMPLETED) { echo "Process completed successfully!"; echo "Final payload: " . json_encode($result->payload); }
Asynchronous Execution
// Dispatch to queue $process->dispatch(); // The process will be executed in the background // You can check the status later $process->refresh(); echo "Current state: " . $process->state->value;
Working with Processable Models
Associate processes with Eloquent models:
use App\Models\User; use IBroStudio\Tasks\Concerns\IsProcessableModel; class User extends Model { use IsProcessableModel; // optional public function processUserData(): Process { return $this->processes()->create([ 'type' => DataProcessingProcess::class, 'payload' => DataProcessingPayloadDto::from([ 'data' => $this->toArray() ]) ]); } } // Usage $user = User::find(1); $process = $user->process(DataProcessingProcess::class, DataProcessingPayloadDto::from(['user_id' => 123, 'required_field' => 'value'])); // or $user->dispatch(DataProcessingProcess::class, DataProcessingPayloadDto::from(['user_id' => 123, 'required_field' => 'value'])); // or $process = $user->processUserData()->handle();
Advanced Features
Pausing and Resuming Processes
Tasks can pause processes and generate signed URLs for resumption:
use IBroStudio\Tasks\Exceptions\PauseProcessException; class RequireApprovalTask extends Task { protected function execute(PayloadContract $payload): PayloadContract|array { if ($this->requiresApproval($payload)) { // This will pause the process and generate a resume URL throw new PauseProcessException( task: $this, message: 'Approval required' ); } return $payload; } private function requiresApproval(PayloadContract $payload): bool { return $payload->amount > 10000; } } // Generate resume URL $resumeUrl = $process->resumeUrl(); // Send URL to approver via email/notification Mail::to($approver)->send(new ApprovalRequired($resumeUrl));
Nested Processes
Execute processes within other processes:
class ParentProcess extends Process { protected function getConfig(array $properties = []): ProcessConfigDto { return ProcessConfigDto::from([ 'tasks' => [ PrepareDataTask::class, ExecuteChildProcess::class, // This task will run a sub-process FinalizeTask::class, ], ...$properties, ]); } }
Exception Handling
Handle different types of exceptions:
use IBroStudio\Tasks\Exceptions\AbortProcessException; use IBroStudio\Tasks\Exceptions\SkipTaskException; class ConditionalTask extends Task { protected function execute(PayloadContract $payload): PayloadContract|array { if ($payload->should_abort) { throw new AbortProcessException( task: $this, message: 'Process aborted due to condition' ); } if ($payload->should_skip) { throw new SkipTaskException('Skipping this task'); } // Normal execution return $payload->updateDto(['processed' => true]); } }
Custom Process Configuration
Override configuration for specific needs:
class CustomProcess extends Process { protected function getConfig(array $properties = []): ProcessConfigDto { return ProcessConfigDto::from([ 'tasks' => [ CustomTask::class, ], 'use_logs' => false, // Disable logging 'payload' => CustomPayload::class, // Custom payload class 'queue' => 'high-priority', // Custom queue ...$properties, ]); } }
Testing
The package is thoroughly tested with Pest. Run the tests:
composer test
Testing Your Processes
use function Pest\Laravel\assertModelExists; it('can execute a data processing process', function () { $process = DataProcessingProcess::factory()->create([ 'payload' => DataProcessingPayloadDto::from([ 'data' => ['required_field' => 'test_value'] ]) ]); $result = $process->handle(); expect($result->state)->toBe(ProcessStatesEnum::COMPLETED) ->and($result->tasks)->each(fn (Task $task) => $task->state->toBe(TaskStatesEnum::COMPLETED)) ->and($result->payload->validated)->toBeTrue() ->and($result->payload->processed)->toBeTrue(); assertModelExists($result); }); it('can pause and resume a process', function () { Queue::fake(); $process = ApprovalProcess::factory()->create([ 'payload' => ApprovalPayloadDto::from([ 'amount' => 15000 // This will trigger pause ]) ]); $result = $process->handle(); expect($result->state)->toBe(ProcessStatesEnum::WAITING); // Simulate approval via resume URL get($result->resumeUrl())->assertSuccessful(); $result->refresh(); expect($result->state)->toBe(ProcessStatesEnum::COMPLETED); });
API Reference
Process Methods
handle()
: Execute the process synchronouslydispatch()
: Queue the process for async executiontransitionTo(ProcessStatesEnum $state, ?string $message = null)
: Change process stateupdatePayload(PayloadContract|array $data)
: Update the process payloadresumeUrl()
: Generate signed URL for resuming paused processes
Task Methods
handle(PayloadContract $payload)
: Execute the taskexecute(PayloadContract $payload)
: Override this method for custom task logictransitionTo(TaskStatesEnum $state, ?string $message = null)
: Change task state
Available States
ProcessStatesEnum
PENDING
: Process created but not startedPROCESSING
: Process is currently runningCOMPLETED
: Process finished successfullyABORTED
: Process was aborted due to errorWAITING
: Process is paused and waiting for external action
TaskStatesEnum
PENDING
: Task not yet startedSTARTED
: Task is currently executingCOMPLETED
: Task finished successfullyABORTED
: Task was abortedWAITING
: Task is paused
Available Exceptions
AbortProcessException
: Aborts the entire processPauseProcessException
: Pauses the process for external actionSkipTaskException
: Skips the current task and continues
Contributing
Please see CONTRIBUTING for details.
Development Setup
- Clone the repository
- Install dependencies:
composer install
- Run tests:
composer test
- Check code style:
composer pint
Security Vulnerabilities
Please review our security policy on how to report security vulnerabilities.
Credits
License
The MIT License (MIT). Please see License File for more information.
Changelog
Please see CHANGELOG for more information on what has changed recently.