Under the Hood
Job Wrapping
Executables are not jobs themselves. When dispatched, the package wraps the executable in a real Laravel job class:
| Scenario | Job class used |
|---|---|
| Standard dispatch | ExecutableJob |
| Unique job | ExecutableUniqueJob (implements ShouldBeUnique) |
| Unique until processing | ExecutableUniqueUntilProcessingJob (implements ShouldBeUniqueUntilProcessing) |
| Sync execution | ExecutableSyncJob (not queueable) |
| Test mode (queueable) | ExecutableJob with FakeJob attached |
| Test mode (sync) | ExecutableSyncJob (not queueable) |
The package selects the right class based on the resolved configuration. The wrapper jobs implement ShouldQueue and use the standard Laravel traits (InteractsWithQueue, Queueable, Batchable, SerializesModels). They are processed by the queue worker exactly like native jobs. Horizon, Pulse, and any queue monitoring tools see them as regular jobs.
Model Serialization
When a queued job runs, Eloquent models are re-fetched from the database. By default, any relations that were loaded at dispatch time are also re-queried. Laravel offers #[WithoutRelations] to prevent this on both the class and individual constructor properties:
// Laravel: class-level or per-property
#[WithoutRelations]
class ProcessPodcast implements ShouldQueue
{
public function __construct(
#[WithoutRelations] // Laravel supports this per-property, executables do not
public Podcast $podcast,
) {}
}Executables support #[WithoutRelations] at the class level only (not per-argument), but add two things Laravel doesn't have:
- A
configure()hook:$config->withoutRelations() - A global default via
config/executable.php:
'serialize_models_with_relations' => false, // default: true (Laravel's default)This disables relation re-loading for all executables at once, so each executable only runs the queries it actually needs.
Resolution order (first non-null wins):
- Per-executable:
#[WithoutRelations]attribute,public bool $withoutRelationsproperty, or$config->withoutRelations() - Global:
config('executable.serialize_models_with_relations')
Horizon Integration
ExecutableJob::tags() generates Horizon tags automatically from any Eloquent model or collection passed as an execute argument (e.g., App\Models\User:1, App\Models\Order:42). If the executable defines its own tags() method, that takes precedence.
ExecutableJob::displayName() returns the executable class name by default, or delegates to the executable's displayName() method if defined.
Middleware Resolution
Like Laravel, job middleware is resolved by merging two sources:
- The
public array $middlewareproperty on the executable - The return value of the
middleware()method on the executable
Both are combined and passed to the queue worker. The difference: the middleware() method receives execute-arguments by name, so middleware can depend on runtime input.
