Conditional validation on Laravel's Form Request

Reading time ~4 minutes

Introduction

In my opinion, Laravel Form Requests are one of the most useful components of Laravel. They can perform authorization and validation on requests, without even touching the controller.

Form Requests are also very flexible, because they are completely customizable by method overriding.

When creating a Form Request, the first method to implement is the authorization() method, which can be used to check if the authenticated user can perform the action. For example the following code checks through gates if the user is allowed to update a post.

public function authorize()
{
    $post = Post::find($this->route('id'));

    return $post && $this->user()->can('update', $post);
}

The code $this->route('id') retrieves the URI parameter named id, defined in our route file as Route::put('post/{id}').

If you don’t need authorization just return true.

The second method to define, is of course the one that adds the validation rules and it’s called (as you may probably guess) rules().

public function rules()
{
    return [
        'title' => 'required|unique:posts|max:255',
        'body'  => 'required',
    ];
}

In here you can use Laravel’s available validation rules to validate your request. Laravel will automatically take care of redirects/AJAX responses if the validation fails.

The Problem

Sometimes you have to cover more complex validation scenarios, for which the validation rules are not powerful enough. For this purpose have two solution. The first is to add an “after” hook to the form request, using the withValidator method. This function receives the instance of the Validator class and allows you to call any of its methods before the validation rules are actually evaluated.

public function withValidator($validator)
{
    $validator->after(function ($validator) {
        if (strpos($this->input('title'), 'bad words') !== false) {
            $validator->errors()->add('title', 'The title cannot contain bad words :(');
        }
    });
}

The second solution is more powerful, but it requires to override entirely the validator function.

For example, let’s say that we want to add a rule that checks if the fiscal_code field is present in the request only if the company type is private and the country is Italy. We cannot express this scenario using the available rules because required_if:anotherfield,value,... accepts only one field and multiple values. Therefore we need to add the following:

use Illuminate\Validation\Factory;

[...]

public function validator(Factory $factory)
{
    $validator = $factory->make($this->input(), $this->rules(), $this->messages(), $this->attributes());

    $validator->sometimes('fiscal_code', 'required', function($input) {
        return $input->company_type === 'private' && $input->country_code === 'IT';
    });

    return $validator;
}

The signature of the Validation factory is the following:

  • array $data - The input data of our form request. We pass them using $this->input()
  • array $rules - The rules of our request
  • array $messages - The customized error messages
  • array $attributes - The customized attributes of our request

The last two parameters deserve further explanation.

In the form request we can override the messages method to customize the error messages returned by the validator:

public function messages()
{
    return [
        // Here we explicitly define the message and the attribute
        'title.required' => 'The Post requires a title',
        // Here we let Laravel to fill the placeholder with the name of the attribute
        'body.required'  => 'The :attribute field is required',
    ];
}

As you can see, in the second case we let Laravel handle the message, filling the placeholder with the name of our attribute. This is exactly the default behavior of Laravel.

This leads us to the last overridable method attributes() which allows to customize the name of the attributes, so instead of “The body field is required” we can get for example “The Post Body field is required”.

public function attributes()
{
    return [
        'title' => 'Title',
        'body'  => 'Post Body',
    ];
}

Pretty useful!

Confirm form submission without loops using jQuery

How to confirm form submission using jQuery without occurring into infinite loops Continue reading