Skip to Content
Course content

131: Laravel Queues and Jobs

Click on the "Edit" button in the top corner of the screen to edit your slide content.

A few years ago, I was reviewing a PR for a junior dev who had built a sleek onboarding flow. Everything looked great until I actually tested the "Sign Up" button. I clicked it, and the browser just... hung. Ten seconds later, the page finally loaded. I dug into the code and found that the controller was sequentially sending a welcome email, pinging a CRM API, and generating a PDF welcome kit before returning the response to the user. The user was essentially waiting for three different external servers to respond before they could even see their dashboard.

That's a classic mistake. In a web environment, the user's request should be as short as possible. If a task doesn't need to happen *right this second* for the page to render, it shouldn't be happening in the request cycle. This is where Laravel Queues come in. They allow you to offload time-consuming tasks to a background process, letting you tell the user "Success!" immediately while the server handles the heavy lifting in the background.

Defining Work with Job Classes

In Laravel, a "Job" is simply a class that represents a specific task. Instead of putting your logic directly in a controller, you wrap it in a Job class. You can generate one quickly using php artisan make:job ProcessNewsletterSubscription. When you open that file, you'll see a handle() method. This is where the actual work lives.

I always recommend passing only the necessary IDs into the Job's constructor rather than passing full Eloquent models. Why? Because the Job is serialized and stored in your database or cache. If you pass a whole User model and that user changes their email address between the time the job is dispatched and the time it's executed, the job will be working with stale data. Pass the ID, and fetch the fresh model inside the handle() method.

public function handle(NewsletterService $service)
{
    // We fetch the fresh user record inside the handler
    $user = User::findOrFail($this->userId);
    $service->subscribe($user);
}

To trigger this, you just call ProcessNewsletterSubscription::dispatch($user->id);. At that point, Laravel pushes a record onto your queue and immediately continues to the next line of code in your controller.

Managing the Queue Pipeline

Now, the job is "dispatched," but it isn't actually running yet. It's just sitting in a list. To make it move, you need a queue driver and a worker. By default, Laravel uses the sync driver, which—to be honest—is useless for actual background processing because it executes the job immediately in the current request. For anything real, you'll want to switch your QUEUE_CONNECTION in the .env file to database or redis.

If you go with the database driver, you'll need to run the migrations to create the jobs table. Once that's set, you need a process running on your server to watch that table and execute the jobs. That's what the worker does:

php artisan queue:work

In a production environment, you wouldn't just run this in your terminal and hope for the best. You'd use a process monitor like Supervisor to ensure that if the queue:work process crashes, it gets restarted automatically. I've spent way too many hours debugging "why aren't my emails sending?" only to realize the worker had crashed three days prior.

Handling Failure and Retries

External APIs are flaky. They time out, they hit rate limits, and they go down for maintenance. If your job fails, you don't want it to just vanish into the void. Laravel gives you an elegant way to handle this by defining a $tries property on your job class.

By setting public $tries = 5;, Laravel will attempt to run the job again if an exception is thrown. You can even use backoff to tell Laravel to wait a few seconds or minutes between attempts, giving the external API time to recover. If the job still fails after all attempts, it gets moved to a failed_jobs table. You can then inspect the error and use php artisan queue:retry all to push them back into the queue once you've fixed the underlying bug.




📋 Practical Task

Implementing a Background PDF Invoice Generator

You are working on an accounting app. Currently, the app generates a PDF invoice using a heavy library and emails it to the client directly inside the InvoiceController@store method, causing the UI to freeze for several seconds.

Your task:

  • Create a new Job class called GenerateInvoicePdf.
  • Modify the Job to accept an $invoiceId in its constructor.
  • In the handle() method, simulate the PDF generation and emailing process (you can use Log::info("Generating PDF for invoice {$this->invoiceId}"); and sleep(3); to simulate the delay).
  • Update the InvoiceController@store method to dispatch this job instead of running the logic inline.
  • Ensure the job is configured to retry up to 3 times in case of a simulated failure.
Rating
0 0

There are no comments for now.

to be the first to leave a comment.