cline / sequencer
Sequential migration and operation orchestration for Laravel
Requires
- php: ^8.5.0
- cline/variable-keys: ^2.0.1
- facade/ignition-contracts: ^1.0.2
- spatie/laravel-package-tools: ^1.93.0
Requires (Dev)
- cline/coding-standard: ^2.0.1
- laravel/pulse: ^1.6.0
- laravel/telescope: ^5.18.0
- orchestra/testbench: ^10.9
- symfony/var-dumper: ^7.4.6 || ^8.0
- timokoerber/laravel-one-time-operations: ^1.4.5
Suggests
None
Provides
None
Conflicts
None
Replaces
None
README
A powerful Laravel package that orchestrates sequential execution of migrations and operations. Ensures database changes and business logic run in chronological order during deployments, preventing conflicts and maintaining data integrity.
Requirements
Requires PHP 8.5+ and Laravel 11+
Installation
composer require cline/sequencer
Quick Start
Create an operation, implement the handle() method, and execute sequentially with php artisan sequencer:process. See Getting Started for detailed examples.
Deferred application work
sequencer:deferred-process --limit=100 processes due intents stored by
Sequencer::defer(). It claims one record immediately before execution. A lost
reservation becomes eligible again after sequencer.deferred.retry_after_seconds
(default 300, or SEQUENCER_DEFERRED_RETRY_AFTER_SECONDS). Set this above the
longest handler execution time before enabling a processor. Reservations count
as attempts, including interrupted attempts; an abandoned final attempt becomes
Failed without executing again. Existing rows need no schema migration. Drain
old-version deferred processors before starting upgraded consumers: only upgraded
workers enforce replacement-claim ownership.
Recovery is at least once, not exactly once: a handler can perform an external effect before its worker disappears. Handlers must tolerate replay. Without runtime containment, expiry does not stop an expired worker, but its later completion/failure cannot overwrite a replacement claim's state. Each invocation visits a record at most once, even when retry delay is zero; it leaves fresh reservations and terminal rows alone. Candidate keys are selected at invocation start; each reservation is rechecked and persisted just before its handler runs. Records becoming due or newly created during execution wait until the next invocation. A vetoed model save does not execute a handler or count as a completed reservation.
To consume only selected work, repeat the option with exact stored identifiers:
php artisan sequencer:deferred-process --operation=registration_credentials --operation=registered_user_shipping_methods
Unselected rows remain unchanged, including expired reservations. Without an
option the command still processes all eligible identifiers. Programmatic
DeferredOperationProcessor::processDue(100, ['registration_credentials']) uses
the same selection; null means unrestricted and [] means no work. Select
the exact persisted alias (including whitespace), not its mapped class name.
Schedule restricted commands separately when environments require different work.
Exact selection supports SQLite, MySQL/MariaDB, PostgreSQL and SQL Server,
independently of their default collation. A custom unsupported database driver
rejects restricted consumption instead of broadening it; unrestricted processing
is unchanged.
Set sequencer.deferred.erase_payload_for to a list of exact stored identifiers
whose inputs should not remain in history. The processor replaces their payload
with [] on Completed (including a handler's successful no-op) or Failed.
Retryable attempts retain inputs; other identifiers retain their normal payloads.
This does not scrub handler error messages, logs, existing terminal history or
caller-written Cancelled records. Account erasure/cancellation must clear those
records at the application lifecycle boundary.
Optional bounded CLI runtime
Enable sequencer.deferred.runtime.enabled (or
SEQUENCER_DEFERRED_RUNTIME_ENABLED=true) to bound the whole command run,
including blocking handler I/O. The default remains disabled, so existing
programmatic calls and the command's concrete type and constructor are unchanged.
When enabled, invoke the real external php artisan sequencer:deferred-process
CLI; direct handle(), synthetic inputs and nested Artisan::call() are rejected
before any claim. Repeatable --operation, --limit and global options are
preserved across supervision.
This opt-in mode requires Unix CLI PHP with PCNTL and POSIX, plus an executable
GNU coreutils timeout (default /usr/bin/timeout). These are not new Composer
platform requirements when the feature is disabled. Install the prerequisites on
every consumer host/image and drain existing unbounded processors before enabling
it. Publish/rebuild cached configuration as part of that rollout.
| Runtime setting | Environment variable | Default |
|---|---|---|
timeout_seconds |
SEQUENCER_DEFERRED_RUNTIME_TIMEOUT_SECONDS |
240 |
kill_after_seconds |
SEQUENCER_DEFERRED_RUNTIME_KILL_AFTER_SECONDS |
10 |
recovery_margin_seconds |
SEQUENCER_DEFERRED_RUNTIME_RECOVERY_MARGIN_SECONDS |
50 |
timeout_binary |
SEQUENCER_DEFERRED_RUNTIME_TIMEOUT_BINARY |
/usr/bin/timeout |
All three durations must be positive integers and their sum must not exceed
deferred.retry_after_seconds. With the defaults, expiry must be at least 300
seconds. The supervisor sends TERM at the timeout and KILL after the grace period;
an independent OS alarm also stops the PHP worker if its supervisor disappears.
The budget is not renewed between handlers. Interrupted claims retain their
existing retry/expiry behavior, and unclaimed work waits for another invocation.
This does not bound direct DeferredOperationProcessor::processDue() calls or
queued operation jobs. It also cannot undo an external effect accepted before a
worker dies: replay is still at least once, so handlers must remain replay-safe.
Documentation
- Getting Started - Installation, configuration, and first operation
- Basic Usage - Core operations and sequential execution
- Monitoring & Status - Check pending, completed, and failed operations
- Orchestration Strategies - Sequential, batch, transactional, dependency-based, and scheduled execution
- Advanced Operations - Retries, timeouts, batching, chaining, middleware, unique operations, encryption, tags, lifecycle hooks
- Programmatic Usage - Facade API, conditional execution, status checks, error handling
- Events - Operation lifecycle events for logging, monitoring, and custom workflows
- Rollback Support - Automatic rollback on failures
- Dependencies - Explicit operation ordering
- Conditional Execution - Runtime execution conditions
- Skip Operations - Skip operations at runtime with SkipOperationException
- Advanced Usage - Transactions, async operations, observability
Key Features
- ✅ Orchestration Strategies - Sequential, batch, transactional, dependency-based, scheduled execution
- ✅ Flexible Execution - Switch strategies via config or fluent API
- ✅ Dependency Resolution - Explicit operation dependencies with topological sorting
- ✅ Conditional Execution - Skip operations based on runtime conditions
- ✅ Async Operations - Queue operations for background processing
- ✅ Retry Mechanisms - Automatic retries with configurable backoff
- ✅ Rollback Support - Automatic rollback of executed operations when failures occur
- ✅ Encryption - Automatic payload encryption for sensitive operations
- ✅ Lifecycle Hooks - Before/after/failed callbacks for operation execution
- ✅ Monitoring - Pulse/Telescope integration with lifecycle events
- ✅ Testing Helpers - Comprehensive testing support
- ✅ Atomic Locking - Prevent concurrent execution in multi-server environments
Change log
Please see CHANGELOG for more information on what has changed recently.
Contributing
Please see CONTRIBUTING and CODE_OF_CONDUCT for details.
Security
If you discover any security related issues, please use the GitHub security reporting form rather than the issue queue.
Credits
License
The MIT License. Please see License File for more information.