How I structure Laravel Request Validators

Posted Monday 27th November, 2023

By Rob Watson

Laravel PHP

Something that's always bugged me with Laravel is how the FormRequest classes try to do too much. With the Single Responsibility Principle we should only be doing 1 thing in 1 place. I am of the opinion that the FormRequest classes do 3 things:

  1. Hold the incoming Request data and properties
  2. Authorise the incoming Request (usually by applying a Gate in my code)
  3. Validate the Request data

I've also seen people use the afterValidation method to format the request data (e.g. transform date strings to Carbon instances). So I suppose that's a 4th thing!

An example

class LoginController
{
  public function __invoke( LoginRequest $request )
  {
    $credentials = $request->validate();

    // ... actual login functionality here
  }
}

class LoginRequest extends FormRequest
{
  // for logging in, we don't need to use the authorize method, it allows by default

  public function rules(): array
  {
    return [
      'email' => 'required|email|max:255',
      'password' => 'required|string|max:255',
    ];
  }
}

A solution

Using Laravel's Dependency Injection feature gives us the ability to request other objects in our Controllers, and those objects will be run through the DI container to build their dependencies. This gives us a way to separate our Request, Validation, Authorisation, and Data into separate units.

I don't use Authorisation through FormRequests, so I won't cover that here. I prefer Middleware and Gates for this task.

Using the login example from above, we have this "request data" class:

final readonly class LoginRequestData
{
  public string $email;
  public string $password;

  public function __construct( LoginRequest $request )
  {
    $this->email = $request->validated('email');
    $this->password = $request->validated('password');
  }
}

The base request stays the same, but in our controller we now ask for the data class:

final class LoginController
{
  public function __invoke( LoginRequestData $data )
  {
    // request is already validated
    // you get type hinting in your IDE when you type $data->
  }
}

Taking it further

I like to use Value Objects (a domain-driven-design term about adding further names to your variable types). The most common example is an Email address. At its core, an Email Address Value Object could look like this:

readonly class EmailAddress
{
  public function __construct( public string $value )
  {
    // todo: validate that incoming $value is a valid email address
  }
}

Then anywhere in your application that you need an email address, you can type hint it:

function doSomething( EmailAddress $email ) {
  // now you know that you have an email address to work with
}

Employing this pattern in a request data class is trivial:

final readonly class LoginRequestData
{
  public EmailAddress $email;
  public string $password;

  public function __construct( LoginRequest $request )
  {
    $this->email = new EmailAddress( $request->validated('email') );
    $this->password = $request->validated('password');
  }
}

Easy.

Ending

I find this pattern really nice to tidy up my Controllers (only a single dependency to inject there - the Request Data object), and some sweet separation of responsibility from the built-in Laravel classes. Plus type-hinting in your IDE when you need to actually use the request data!

Thanks for reading, catch you next time.