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.