Laravel Middleware and Dependency Injection

As you may or not know you are not able in to instantiate Laravel middleware yourself which makes dependency injection just that little bit trickier.

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 $value;

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

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 CheckPassword
{
    /**
     * @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(Password::class, function ($app) {
            return new Password(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.