DEV Community

David Berri
David Berri

Posted on • Originally published at dberri.com on

Build validation that you can be proud of in Laravel

Today I wanted to bring you the topic of validation in the backend using Laravel. For me, this is part of what makes this framework so elegant and fluent. Even though it allows you validate your input data using multiple approaches, it gives you guard rails to ensure that it will work well and will stay organized as your application grows.

Let's jump into the code. I created a starter project that you can use to follow along. Just clone the repo from here. It's a simple CRUD project with a Rest API to show, store, update and remove bird sightings from the application. Currently it doesn't have any validation at all. Whatever it receives from the clients, it will try to store or update in the database, which is can be very bad even if you have validation on the client code. Let's start fixing that with Form Requests.

Form Requests

To be honest, if you have only a couple of rules, you might not need a Form Request, you can just validate the request inside the controller method like:

// app/Http/Controllers/BurdSightingController.php

public function create(Request $request)
{
    $request->validate([
        'common_name' => 'required|string|max:255',
    ]);

    $sighting = BirdSighting::create($request->validated());
    return response()->json($sighting, 201);
}

Enter fullscreen mode Exit fullscreen mode

But if you want a cleaner interface with the controller and a special place where all the validation happens, then you can create a FormRequest using an artisan command:

php artisan make:request CreateBirdSightingRequest

Enter fullscreen mode Exit fullscreen mode

This will create the following file:

// app/Http/Requests/CreateBirdSightingRequest.php

class CreateBirdSightingRequest extends FormRequest
{
    public function authorize()
    {
        return false;
    }

    public function rules()
    {
        return [
            //
        ];
    }
}

Enter fullscreen mode Exit fullscreen mode

The authorize method can be used to determined if the current user is authorized to perform that operation, so you can, for example, make use of Gates and Policies or even simpler rules like if the user has a specific property. Make sure to return a boolean in that method: true means that the user is authorized and false means the user is not authorized and will respond with a 403 HTTP status response.

The rules method is where the actual validation rules go. Laravel has dozens of built-in validation rules to add here. So, in our case, we want to validate fields in order to create a bird sighting:

    public function rules()
    {
        return [
            'common_name' => 'required|string|max:255',
            'species' => 'required|string|max:255',
            'sighted_at' => 'required|date',
            'quantity' => 'nullable|integer|min:1',
            'latitude' => 'required|numeric',
                        'longitude' => ['required', 'numeric'],
        ];
    }

Enter fullscreen mode Exit fullscreen mode

You will notice that for longitude I used a different syntax. Laravel supports both strings with rules a separated by |and arrays with rules are separated by commas. As far as I know, the string syntax can only be used with built-in rules. If you create a custom validation rule (and I'll show you how) then you have to use the array syntax.

Custom validation messages

Form Requests also allow you to add custom validation messages for each field and rule. TO do that you need to override the messages method in the Form Request you created, for example:

    public function messages()
    {
        return [
            'common_name.required' => 'A common name for the bird you saw is required',
            'sighted_at.required' => 'An approximate date and time for the sighting is required',
        ];
    }

Enter fullscreen mode Exit fullscreen mode

These messages will override the default validation message for the required rule which you can find in resources/lang/validation.php.

Finally, you have to use this Form Request in your method, so go back to the controller and replace Request $requestwith CreateBirdSightingRequest $request

    public function create(CreateBirdSightingRequest $request)
    {
        $sighting = BirdSighting::create($request->validated());
        return response()->json($sighting, 201);
    }

Enter fullscreen mode Exit fullscreen mode

You will also notice that we replaced $request->all() with $request->validated() to retrieve the validated fields from the request.

Now if you send data to that endpoint and it does not conform to the rules you added, you'll get a 422 HTTP status response with the error messages for each field.

Organizing rules

Now you will need another form request for the update method which might have a different set of rules and speaking of set of rules, when the application starts to grow and rules start to get too "scattered" in the form request, we can create rule classes that make those sets reusable and more legible. I like to create a Rules directory inside app/ and create a class that will provide the rules I need:

// app/BirdSightingRules.php

class BirdSightingRules
{
    public static function birdRules()
    {
        return [
            'common_name' => 'required|string|max:255',
            'species' => 'required|string|max:255',
            'quantity' => 'nullable|integer|min:1',
        ];
    }

    public static function locationRules()
    {
        return [
            'sighted_at' => 'required|date',
            'latitude' => 'required|numeric',
            'longitude' => ['required', 'numeric'],
        ];
    }

    public static function updateRules()
    {
        return [
            // 
        ];
    }
}

Enter fullscreen mode Exit fullscreen mode

Then back in the form request:

    public function rules()
    {
        return array_merge(
            BirdSightingRules::birdRules(),
            BirdSightingRules::locationRules()
        );
    }

Enter fullscreen mode Exit fullscreen mode

Custom rules

Laravel has so many built-in rules that it might take some time for you to need to build a custom rule, but sometimes your application might just have a specific trait that requires a custom validation rule. If that's the case, artisan has you covered:

php artisan make:rule SightedInMarch

Enter fullscreen mode Exit fullscreen mode

It will create a file under app/Rules which contains a custom rule with the following methods:

// app/Rules/SightedInMarch.php

class SightedInMarch implements Rule
{
    public function passes($attribute, $value)
    {
        //
    }

    public function message()
    {
        return 'The validation error message.';
    }
}

Enter fullscreen mode Exit fullscreen mode

Inside the passes method you can create your custom rule which needs to return a boolean and inside message you just need to return a string with an explanation of why it failed the validation:

    public function passes($attribute, $value)
    {
        return preg_match('/^[0-9]{4}-03-[0-9]{2}/', $value);
    }

    public function message()
    {
        return 'Why are you watching birds in March?';
    }

Enter fullscreen mode Exit fullscreen mode

Then you go back to you set of rules and use this custom rule with the sighted_at attribute:

// app/Rules/BirdSightingRules.php

    public static function locationRules()
    {
        return [
            'sighted_at' => ['required', 'date', new SightedInMarch],
            'latitude' => 'required|numeric',
            'longitude' => ['required', 'numeric'],
        ];
    }

Enter fullscreen mode Exit fullscreen mode

Now all form requests that use locationRules will automatically validate the sighted_at attribute using that new custom rule. And with that, we wrap another article. See you on the next one!

Top comments (0)