colinlongworth's avatar

Queue/Job Middleware Execution Order

I'm seeking a sanity check on my job middleware, specifically for handling HTTP requests using Saloon and Rate Limiting. Currently, I do this:

public function middleware(): array
    {
        return [
            new ThrottlesExceptionsWithRedis(3, 65),
            new MyRateLimitMiddleware()
        ];
    }

Where the custom middleware does something like this:

And a job itself might do this:

public function handle(): void
{
    $client = saloon_client();

    $users = $client->users->list();

    foreach ($users->items() as $user) {
        //Do something with each user
    }

}

I want to confirm the order of the middleware is right, as to me, it's not intuitive and I'm getting conflicting answers when I search it. What I want to happen is for MyRateLimitMiddleware to catch the specified HTTP errors, and then ThrottlesExceptionsWithRedis to catch everything else. My understanding is that MyRateLimitMiddleware has to be last in the middleware stack to do this, as the last middleware specified wraps all previous.

Is this correct?

0 likes
2 replies
LaryAI's avatar
Level 58

You're correct in your understanding of how Laravel job middleware stacks work, but let's clarify with some details and an example.

Middleware Execution Order

Laravel job middleware is executed in the order you define them in the middleware() array, from first to last. However, the way the middleware chain works is that the first middleware wraps the next one, and so on, with the last middleware being the innermost (closest to your job's handle() method).

So, given this:

public function middleware(): array
{
    return [
        new ThrottlesExceptionsWithRedis(3, 65),
        new MyRateLimitMiddleware()
    ];
}

The execution order is:

  1. ThrottlesExceptionsWithRedis (outermost)
  2. MyRateLimitMiddleware
  3. Your job's handle() method (innermost)

This means:

  • Exceptions thrown in your job's handle() method will first be caught by MyRateLimitMiddleware.
  • If MyRateLimitMiddleware does not handle (catch) the exception, it will bubble up to ThrottlesExceptionsWithRedis.

What You Want

You said:

I want MyRateLimitMiddleware to catch the specified HTTP errors, and then ThrottlesExceptionsWithRedis to catch everything else.

Your current order is correct for this behavior.

  • MyRateLimitMiddleware will handle RateLimitReachedException and FatalRequestException.
  • Any other exceptions will bubble up to ThrottlesExceptionsWithRedis.

Visual Example

Here's a simplified visualization:

ThrottlesExceptionsWithRedis
    └── MyRateLimitMiddleware
            └── handle()

In Summary

  • Order matters: The first middleware in the array is the outermost.
  • Your current order is correct for your desired behavior.

Reference

From the Laravel docs:

When a job is dispatched, the middleware will be executed in the order they are listed in the middleware method.

TL;DR

Yes, your order is correct. The last middleware in the array is the innermost, so it will catch exceptions from your job first. If it doesn't handle them, they bubble up to the previous middleware.

If you want MyRateLimitMiddleware to catch specific exceptions first, and ThrottlesExceptionsWithRedis to handle the rest, keep your current order:

public function middleware(): array
{
    return [
        new ThrottlesExceptionsWithRedis(3, 65),
        new MyRateLimitMiddleware()
    ];
}
Glukinho's avatar

Just add to both middlewares:

Log::info(__CLASS__);

and look which class name appears first in log and which is second.

Please or to participate in this conversation.