Search code examples
phplaravelredisthrottlingrate-limiting

Throttling with Redis Only on Production in Laravel


Background

Out of the box, Laravel provides two middleware that can be used for rate limiting (throttling):

\Illuminate\Routing\Middleware\ThrottleRequests::class
\Illuminate\Routing\Middleware\ThrottleRequestsWithRedis::class

As the documentation states, if you are using Redis as the cache driver, you can change the mapping in the Kernel.php like so:

/**
 * The application's middleware aliases.
 *
 * Aliases may be used instead of class names to conveniently assign middleware to routes and groups.
 *
 * @var array<string, class-string|string>
 */
protected $middlewareAliases = [
    // ...
    'throttle' => \Illuminate\Routing\Middleware\ThrottleRequestsWithRedis::class
    // ...
];

The Issue

The issue with this is that the above is not dynamic, dependent on the environment. For instance, on my staging and production environments, I utilise Redis, but on my local and development environments, I do not.

Potential Solution

There is an obvious dirty fix, something like this (Kernel.php):

/**
 * The application's middleware aliases.
 *
 * Aliases may be used instead of class names to conveniently assign middleware to routes and groups.
 *
 * @var array<string, class-string|string>
 */
protected $middlewareAliases = [
    // ...
    'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class
    // ...
];

/**
 * Create a new HTTP kernel instance.
 *
 * @param  \Illuminate\Contracts\Foundation\Application  $app
 * @param  \Illuminate\Routing\Router  $router
 * @return void
 */
public function __construct(Application $app, Router $router)
{
    if ($app->environment('production')) {
        $this->middlewareAliases['throttle'] = \Illuminate\Routing\Middleware\ThrottleRequestsWithRedis::class;
    }

    parent::__construct($app, $router);
}

Is there a 'standard' way to achieve this without having to override the Kernel constructor? Essentially, I would like my app to dynamically choose the relevant middleware dependant on whether the environment is set to production (or, the default cache store is set to redis).

Update

The above solution does not work as the Kernel is accessed prior to the app being bootstrapped, thus the environment is not available at this point. The other solution I am now looking into is extending the base ThrottleRequests class in order dynamically call the relevant class.


Solution

  • After much research and testing, I concluded that the best solution was to dynamically set the throttle middleware in the RouteServiceProvider like so:

    class RouteServiceProvider extends ServiceProvider
    {
        /**
         * Bootstrap any application services.
         *
         * @return void
         */
        public function boot(): void
        {
            $this->registerThrottleMiddleware();
        }
    
        /**
         * Register the middleware used for throttling requests.
         *
         * @return void
         */
        private function registerThrottleMiddleware(): void
        {
            $router = app()->make('router');
    
            $middleware = config('cache.default') !== 'redis'
                ? \Illuminate\Routing\Middleware\ThrottleRequests::class
                : \Illuminate\Routing\Middleware\ThrottleRequestsWithRedis::class;
    
            $router->aliasMiddleware('throttle', $middleware);
        }
    }