Comparison with Laravel Native Jobs
Executables are a drop-in replacement for Laravel jobs. Every standard feature is supported. This page confirms full parity and highlights what executables add on top.
Dispatching
Laravel parity
| Capability | Laravel Job | Executable |
|---|---|---|
| Dispatch to queue | MyJob::dispatch($arg) | MyExec::onQueue()->execute($arg) |
| Dispatch synchronously | MyJob::dispatchSync($arg) | MyExec::sync()->execute($arg) |
| Dispatch after response | MyJob::dispatchAfterResponse($arg) | MyExec::sync()->afterResponse()->execute($arg) |
| Conditional dispatch | MyJob::dispatchIf($cond, $arg) | MyExec::onQueue()->when($cond)->execute($arg) |
| Conditional skip | MyJob::dispatchUnless($cond, $arg) | MyExec::onQueue()->unless($cond)->execute($arg) |
| Prepare without dispatch | new MyJob($arg) | MyExec::prepare()->execute($arg) |
What executables add
| Capability | How |
|---|---|
| Multiple conditions | ->when($a)->when($b)->unless($c) |
Closure jobs (dispatch(function () { ... })) are not supported. Executables are class-based by design.
Job Configuration
Laravel parity
All Laravel job configuration works on executables:
| Feature | Supported |
|---|---|
Max tries ($tries) | Yes |
Timeout ($timeout) | Yes |
Backoff ($backoff) | Yes |
| Max exceptions | Yes |
| Fail on timeout | Yes |
| Delay | Yes |
| Connection / queue | Yes |
| Chain connection / queue | Yes |
| After commit / before commit | Yes |
| Delete when missing models | Yes |
Encryption (ShouldBeEncrypted) | Yes |
Unique jobs (ShouldBeUnique) | Yes |
| Unique until processing | Yes |
Custom unique cache (uniqueVia()) | Yes |
| Display name | Yes |
| Tags (Horizon) | Yes |
| Middleware | Yes |
| Retry until | Yes |
| Without relations | Yes (class-level only, per-argument not supported since execute-arguments aren't constructor properties) |
| Failed handler | Yes |
What executables add
| Feature | How |
|---|---|
configure() hook | Dynamic config based on execute-arguments, in a single chainable method |
| Dispatch-time overrides | ->withTries(), ->timeout(), ->shouldBeEncrypted(), etc. The caller controls config |
->shouldBeUnique() at dispatch | Enable uniqueness without implementing the interface |
| Global without-relations default | config('executable.serialize_models_with_relations') |
Job Interaction (inside execute)
Laravel parity
| Method | Supported |
|---|---|
$this->attempts() | Yes |
$this->delete() | Yes |
$this->fail($e) | Yes |
$this->release($delay) | Yes |
$this->batch() | Yes |
$this->prependToChain($job) | Yes |
$this->appendToChain($job) | Yes |
Note
The raw underlying job instance is accessible via $this->executableJob->job (equivalent to $this->job on a Laravel job).
Middleware
Laravel parity
All Laravel queue middleware works unchanged: WithoutOverlapping, RateLimited, ThrottlesExceptions, Skip, and any custom middleware.
| Feature | Supported |
|---|---|
middleware() method | Yes |
public $middleware property | Yes |
| Merge property + method | Yes |
What executables add
| Feature | How |
|---|---|
| Assert middleware in tests | PushedJob::assertHasMiddleware() |
Chains
Laravel parity
| Feature | Supported |
|---|---|
| Inline chain on dispatch | Yes, MyExec::onQueue()->chain([...])->execute() |
Bus::chain() | Yes, with prepared executables |
| Chain connection/queue | Yes |
| Prepend/append to chain | Yes |
Batches
Laravel parity
Batching delegates entirely to Laravel's Bus::batch(). All batch features work:
| Feature | Supported |
|---|---|
->then() / ->catch() / ->finally() | Yes |
->progress() | Yes |
->allowFailures() | Yes |
->name() / ->onQueue() / ->onConnection() | Yes |
$this->batch() inside job | Yes |
$this->batch()->cancelled() | Yes |
| Chains inside batches | Yes |
Testing
Laravel parity
| Capability | Laravel Job | Executable |
|---|---|---|
| Assert job pushed | Queue::assertPushed(MyJob::class) | MyExec::assert()->queued() |
| Assert with callback | Queue::assertPushed(MyJob::class, fn) | ->where(fn) on queued assertion |
| Assert count | Queue::assertPushed(MyJob::class, 3) | ->times(3) on queued assertion |
| Assert on queue | Queue::assertPushedOn('q', MyJob::class) | ->onQueue('q') on queued assertion |
| Assert not pushed | Queue::assertNotPushed(MyJob::class) | MyExec::assert()->notQueued() |
| Assert nothing pushed (global) | Queue::assertNothingPushed() | Use Queue::assertNothingPushed() directly |
| Assert pushed with chain | Queue::assertPushedWithChain(...) | ->withChain([...]) on queued assertion |
| Assert batch dispatched | Bus::assertBatched(fn) | Execution::assertBatched(fn) |
| Assert batch count | Bus::assertBatchCount(n) | Execution::assertBatchCount(n) |
| Assert nothing batched | Bus::assertNothingBatched() | Execution::assertNothingBatched() |
What executables add
These have no equivalent in Laravel's native job testing:
| Capability | How |
|---|---|
| Fluent assertion chains | ->onQueue('q')->with($arg)->once(), no callbacks needed |
| Assert arguments directly | ->with($arg1, $arg2) or ->withArgs(fn) |
| Test release/delete/fail without a queue worker | MyExec::test()->execute() + ::assert()->released() / deleted() / failed() |
| Assert chain built during execution | ::assert()->hasChain([...]) / hasNoChain() |
| Mock an executable (replace in container) | MyExec::mock()->shouldExecute()->once()->with($arg) |
| Spy on an executable (real execution + verify) | MyExec::spy() + ::assert()->executed()->once() |
| Inspect individual pushed job properties | PushedJob wrapper with assertIsOnQueue(), assertIsDelayed(), etc. |
| Inspect individual batch properties | PushedBatch wrapper with assertHasName(), assertContains(), etc. |
| Assert delayed / encrypted / unique | PushedJob::isDelayed(), isEncrypted(), isUnique() |
| Assert middleware / tags | PushedJob::hasMiddleware(), hasTags() |
| Assert batch name / queue / callbacks | PushedBatch::assertHasName(), assertContainsThenCallback(), etc. |
| Debug dump pushed jobs | ::assert()->queued()->dump() / Execution::dumpJobs() |
| Debug dump batches | Execution::dumpBatches() |
