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);
}
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
This will create the following file:
// app/Http/Requests/CreateBirdSightingRequest.php
class CreateBirdSightingRequest extends FormRequest
{
public function authorize()
{
return false;
}
public function rules()
{
return [
//
];
}
}
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'],
];
}
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',
];
}
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 $request
with CreateBirdSightingRequest $request
public function create(CreateBirdSightingRequest $request)
{
$sighting = BirdSighting::create($request->validated());
return response()->json($sighting, 201);
}
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 [
//
];
}
}
Then back in the form request:
public function rules()
{
return array_merge(
BirdSightingRules::birdRules(),
BirdSightingRules::locationRules()
);
}
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
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.';
}
}
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?';
}
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'],
];
}
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)