Laravel Middleware and Dependency Injection

Posted on

TLDR; In Laravel, middleware is bound in a way that you may not access their constructor. The best way I have found is to bind whatever the input you want, to the DI container and add it a parameter to the middlewares __construct() method.

The Scenario

Disclaimer: This is not the way to do auth. I have created this scenario to closely resemble a situation I came across in real life.

I had a problem in which I needed to use an env var variable in a middleware. Ideally, I wanted to inject into its __construct method. As this would make it easier to test. However I was struggling to find a way to access the middlewares construct as it is called by the router. In the end the only way I found was to create a complex type, bind it to the DI, and add it as a parameter to the constuctor method.

<?php

namespace App\Environment;

class PasswordValidator
{
	protected $$passwordAnswer;

	public function __construct($passwordAnswer)
	{
		$this->passwordAnswer = $passwordAnswer;
		if ($this->value === "") {
			// throw error PASSWORD NOT SET!!
		}
	}
	public function validate($input): bool
	{
		return $input === $this->passwordAnswer;
	}
}

The above code reads an env var, and throws an error if it is not set.

<?php

namespace App\Http\Middleware;

use App\Auth/PasswordValidator;
use \Closure;

class CheckPasswordMiddleware
{
    /**
     * @var Recaptcha
     */
    protected $passwordValidator

    public function __construct(PasswordValidator $passwordValidator)
    {
        $this->passwordValidator = $passwordValidator
    }

    public function handle($request, Closure $next)
    {
        $inputPassword = $request->input('password');
        if (! $inputPassword) {
            return ErrorService::respondBadRequest();
        }

        if (! $this->passwordValidator->validate($inputPassword)) {
            return ErrorService::respondErrorValidation('auth failed');
        }
        
        return $next($request);
    }
}

This is the middleware itself, if the password param does not match what we have in the env var, we will deny the user access.

<?php

namespace App\Providers;

use App\Auth\Password;
use Illuminate\Support\ServiceProvider;

class AppServiceProvider extends ServiceProvider
{
    /**
     * Register any application services.
     *
     * @return void
     */
    public function register()
    {
        $this->app->bind(PasswordValidator::class, function ($app) {
            return new PasswordValidator(getenv("AUTH_PASSWORD"));
        });
    }
}

This is the main part, essentially anything that you want injecting into a middlewares construct has to go through Laravels DI container. I hope this saves someone some time.