The job dispatching process in Laravel starts when you tell your application to send a task to the queue system. This usually occurs by calling the dispatch method on a job class, using the global dispatch() helper function, or through the Bus facade. Any arguments given to the static dispatch() method on the job class are automatically passed to the job instance.
These dispatch methods serve as entry points to the Laravel queue system. They utilize the Bus component, which is a lower-level part that the queued job dispatching relies on.
When you call a dispatch method, the Bus component starts by constructing one of three “pending” objects: PendingDispatch for single jobs, PendingChain for job chains, or PendingBatch for job batches. These objects prepare your jobs for the queue and provide a user-friendly API for configuring how the job should be pushed and processed.
The Bus component makes key choices about how to manage the job. For example, it verifies if the job is labeled as unique. If a job uses the ShouldBeUnique interface, Laravel will stop it from being dispatched if another version of the job is already in the queue and hasn’t completed processing. This unique job feature operates by attempting to secure an atomic lock with the job’s unique ID (set by the uniqueId property or method), and if the lock cannot be secured, the job won’t be dispatched. It’s crucial to understand that this unique job feature is only relevant when dispatching individual jobs and does not apply to jobs sent in chains or batches through the Bus component.
If you dispatch a series of jobs with Bus::chain(), all jobs are linked to the first job, which is sent out as a regular job, with the others included in its payload.
For job batches sent using Bus::batch(), the batch information is stored in a database table, and the individual jobs in the batch are also prepared for sending.
The job may be sent to the transaction manager next if it is configured to dispatch after the database transactions are committed. This is controlled by the after_commit connection option in your queue configuration or by chaining the afterCommit() method to the dispatch operation. Using afterCommit() guarantees that the job is dispatched to the queue only after all open database transactions have been successfully committed. If a transaction is rolled back, jobs dispatched during that transaction will be discarded and not sent to the queue. Laravel manages this by wrapping the queue push logic in a callback that is held by the DatabaseTransactionsManager until the transaction commits.
When the decision is taken to dispatch the job (either immediately or once transactions are committed), the queue component’s final action before sending the job to the queue store is to create the job payload. This process consists of various stages:
Extracting and assigning configuration attributes: Attributes including maxTries, maxExceptions, failOnTimeout, backoff, timeout, and retryUntil are gathered from the job class (properties or methods) or generated (like uuid). The uuid acts as a unique identifier created during dispatch.
Serializing the job class: The job object, along with its properties and dependencies, is turned into a string format using PHP’s serialize() function. Laravel makes a copy of the job object before serialization to ensure that changes made by magic methods like __sleep() do not impact the original object. When the Serializes Models trait is utilized, Eloquent model instances given to the job constructor are automatically transformed into a simpler ModelIdentifier format during serialization. Binary data needs careful handling, preferably by saving the file and only passing its path (Claim Check pattern) instead of the binary content directly.
Encrypting the serialized job (when needed): If the job class uses the ShouldBeEncrypted interface, the whole serialized job object is encrypted before storage. This ensures that sensitive information in the job payload is not easily accessible in the queue store.
Triggering payload hooks: Laravel executes any callbacks that are registered with Queue::createPayloadUsing(). These callbacks allow you to include custom attributes, like tenant_id, in the job payload.
Transforming the payload array into JSON: The complete array with all job details and configuration settings is converted into a JSON string. This JSON string is what gets sent to the queue store.
For queued event listeners, mailables, notifications, and broadcasted events, implementing the ShouldQueue interface tells Laravel to queue a specific job (for example, CallQueuedListener or CallQueuedClosure) instead of running the task immediately. Laravel transfers queue configuration properties (like $tries and $timeout) from the listener, mailable, or notification to the job that is dispatched. These queueable tasks go straight to the queue component, avoiding the Bus component, which means that features like unique jobs, chaining, and batching cannot be directly applied to them.
When you dispatch a closure, Laravel serializes it using the SerializableClosure component, which converts it into an object that can be stored and invoked later. The code within the closure is signed cryptographically to avoid any modifications during transit. The serialized closure is then wrapped in a CallQueuedClosure job and sent out.
Once the payload is created and JSON encoded, it is sent to the configured Queue Store (e.g., database, Redis, SQS) to await processing by a worker.
This is a simplified version from Laravel Queue in Action by Mohammad Said
