The power of Form Requests in Laravel

Laravel has a very handy feature called form requests. Form requests are custom request classes that take care of validation and authorization. They can be very useful to provide additional functionality for your application.

This post will show some of the power of the form requests.

Authorization

The authorization with a form request is pretty well documented in the Laravel docs so I won’t write too much about this, but I will show a practical example.

Let’s say we have an application with users which can create notes. The notes belong to an user and no other user should be able to edit someone else’s note. To prevent an update request to a note which doesn’t belong to the user, we can use the authorize method.

public function authorize(): bool
{
    // Determine if the note belongs to the current user
    return $note->user_id === $this->route('note')->id;
}

In this case the Note model is resolved via route model binding, that’s why the route method returns the model directly. Otherwise you need to retrieve the note model first.

No authorization needed?

The default form request is generated with an authorize method which returns false. In the case you don’t need any authorization, you could return true but that’s already the default behavior. So when you don’t need any authorization, you can remove the whole authorize method.

Validation

The form request are most used for validation purposes. I really like the idea to extract the logic for validation a request to the form request as it makes the controllers a lot cleaner and assures the rest of your application that the data provided is valid. For more information about validation with a Form request, see the Laravel docs.

Preparing data before validation

Laravel provides a method which is ran before the validation takes place. This way you can prepare your data before validation. Just add a prepareForValidation method.

In this example we post data for a FAQ. The FAQ consists of entries with a question and answer. With js you can add as many entries as you want, but (partially) empty entries might occur. When a question or answer is empty (and turned into null by the middleware), we want to skip the FAQ entry.

protected function prepareForValidation(): void
{
    // Store the entries from the request in a collection
    $entries = collect($this->get('entries'));

    // Store the data in the request
    $this->merge([
        'entries' => $entries
            ->filter(function (array $entry) {
                return !is_null($entry['question']) && !is_null($entry['answer']);
            })
            ->all(),
    ]);
}

After this you can require the question and answer both being filled and your data will no longer contain the (partially) filled entries.

Validating route parameters

By default the form request provides the regular request data. But in some cases you might find it useful to validate a route parameter. Let’s say we offer a service that runs several checks on an ip-address (i.e. 8.8.8.8). The uri becomes /ip-check/{ip}. In this case we want to validate the ip parameter too.

The Form request retrieves the request data with the validationData method. To add route parameters to the validation data, we can use that method.

public function validationData(): array
{
    // Retrieve the route parameters
    $routeParameters = $this
        ->route()
        ->parameters();

    // Retrieve the request data
    $requestData = $this->all();

    // Return both as data for the validation
    return array_merge($requestData, $routeParameters);
}

In the the validation rules will now contain both route parameters as the regular request data.

public function rules(): array
{
    return [
        'ip' => ['required', 'ip'],
    ];
}

Application logic

Besides validation and authorization the form requests, they can also be handy to structure the request data so you are able to use the data more easily. In this example we’re providing a date in the european notation (d-m-Y). This results in the normal validation rules.

'expires_at' => ['required', 'date_format:d-m-Y'],

It would be handy to directly access the date as object. To get the date object, we need to convert the request data. As this is specific for this request, we can add it to the form request.

public function getExpiresAt(): DateTimeInterface
{
    return Carbon::createFromFormat('d-m-Y', $this->get('expires_at'));
}

This enables us to access the date from the form request.

public function store(SomethingStoreRequest $request)
{
    $date = $request->getExpiresAt();
}

In this example we use it for a date conversion, but a lot of variants exists. For example converting combined fields into an object, etc.

Even more?

These are a few examples what you can achieve with form requests. I’m using them a lot and it helps me to keep my code clean.

Do you have any questions, comments or even more use cases for form request? Feel free to talk to me on Twitter.