Are you looking for a slick way for validating your incoming requests on your PHP web application? Or you are already using Laravel's Request Validation, but you want to have deeper understanding on what is going on? Then you are at the right place, because I will try to break this down and explain how the Laravel's Request Validation works under the hood.

History and background

Before we get into details and concrete examples, I think it is a good idea to explain what is the Request Validation. In my opinion, the name is self-explanatory. Simply put, we have an incoming request to our application, and input we receive should match certain rules (validate it) in order to continue and process the request.
If this is a request on our login/registration page, we need to make sure that we have, for example, required values for username and password.
If this is a request on our administration dashboard, probably we will need some pagination parameters. It doesn't matter of which CRUD method type is the request, at some point we will have to do some validation on it.

Let me provide a bit history. Before the time of modern frameworks, things were not that well organized. It required a bit more lines of code to be written in order to do simple validation of the incoming data (even though today there a more files of code instead of lines doing that for you, but at least you do not have to maintain it as part of your application). I have a simple registration attempt validation from a very old application that was refactored.

Here we can see simple validation for attempt to register a user using a simple form that consists of username, password and a password_confirmation that should match the password field. At first look this doesn't look that bad, but if we had 10 more fields, and validating for instance password strength, length, username rules to have only certain characters, length, to be unique, the options are limitless. You might have also seen this picture. It is valid representation, and I would highly recommend to avoid this.

Image credit: Reddit
Image credit: Reddit. Sorry for the quality

If you don't understand where are the $_POST, $_SESSION variables coming from, you should probably check this out and my suggestion would be to start learning plain PHP code, and then compare how things are improved and handled in modern frameworks. In my opinion, that is the way to learn how things work and understand why things are built in the way they are. Just my two cents.

Nowadays, most of the modern frameworks will include some sort of "validation package", that will give you convenient way to validate the incoming requests. Since I have been working with Laravel for a while now, I will just show example how things improved for me when I switched over to Laravel 4.2 from validations similar to the example above. Please note that this example is for Laravel 4.2 (since 2014), and Laravel is now at version 8.x.

So framework does really help to have a good and easy validation. Everything looks nice and verbose. We are creating a $validator instance, which will use the input from the request (basically using the data from $_POST or $_GET variables behind the scenes to take the values), and we apply some rules to check that username and password are mandatory fields, username should be only with alphabetic characters and to not exists in the users table (based on the unique:users rule), the password should be minimum 8 characters and confirmed with a password_confirmation field. You can check the validation options here. So it is really big improvement. But this doesn't seem magical at all, since it makes sense what is going on, it is verbose. But let's see the latest implementation of the request validation.

So even less lines in the controller. Now it is clear when we first see the controller, that we just work with the data ($attributes) which has been previously validated. And we can always trust this. But let's also see what do we have in the CreateNewUserRequest, since we don't see the previously defined rules anywhere now.

So one method rules which should return an array of rules. We have dedicated class that has only one job, to validate the request somehow. Now the RegisterController doesn't know nothing about how the request is validated, how the Validator is created. The controller can just take the validated input, and probably pass it to some business logic layer to register the user.

Please note that this is not the only way to validate using the Laravel framework, nor this is not the only validation strategy. You can check the Laravel's Validation here.

But since we don't see the rules() method being called anywhere in the example, let's dig into the Laravel's source code and figure out how and when the request is validated.

Dependency Injection via method

If you are familiar with Dependency Injection, you are probably now ahead of the curve, and you are aware that the CreateNewUserRequest is resolved using the Laravel's Service Container. And yes, that is correct, CreateNewUserRequest is injected in the store method using Dependency Injection, but we are never using the rules() method.

You can check my post where I have explained how Dependency Injection works behind the scenes

So the rules() method is nowhere called in our application, but if we try and hit our endpoint to register new user, it will fail due to validation error. Let's verify that.

First let's define API endpoint that is calling the controller store method. I am using API example just because I find it easier and more understandable to explain the error and validation instead of using redirects on error, but the example applies on both.

And for the sake of simplicity, the response would be validated attributes from the request.

Example request/response from the api/auth/register endpoint using Postman

So, just a quick conclusion, even though we do not need to call the rules() method from the CreateNewUserRequest, the request is being validated. So let's check if we provide correct attributes, just a sanity check.

Please note that the unique:users rule will require database connection set in order to check the database for the username value.
Example of given valid data for the rules() method to prove our theory that the request is going to be properly validated with simple Dependency Injection on the request class

As seen above, everything seems to work properly. Request is being validated, and the validated attributes are returned as response. Please note that the just_extra_field is not in the response since it has no validation defined in the rules() method. That is just something that the validated() method is doing for you.

Let's see now how this magical feature works behind the hood

Laravel's FormRequest class

Before we start digging into the Laravel's source code, let's just double check and see what we know from the code we wrote.

  1. Our CreateNewUserRequest class is extending the Illuminate\Foundation\Http\FormRequest. This is what we know from Laravel's doc
  2. We have rules() method that is being called somewhere when resolving the CreateNewUserRequest. This validation is executed before the store method starts with execution from the RegisterController
  3. We have the validated() method called inside the [email protected] method.

Okay so, let's start by double checking the first obvious thing, the FormRequest class from Laravel.

The first easy clue to track down and find, is the validated() method. This method doesn't exists on our CreateNewUserRequest, so that means it is coming from the FormRequest. And we already know that we extend the FormRequest with our CreateNewUserRequest.

As it seems in the code, the validator instance is already there, and $this->validator->validated() will only return the validated values of the rules we defined. But we are on the right path, since we found Laravel's Validator instance, so that means somewhere before this, the rules were used to create this instance. Let's check the usages of $this->validator

If you scroll bit down from the validated() method inside FormRequest there is the setValidator(Validator $validator) method defined.

Since this method accepts whole instance, seems like tracking this method down will show how the validator instance was made, and where the rules() method is used in the process. After checking the usages of the setValidator method, it will give only one place where this method is used, and that is inside the getValidatorInstance() method.

With a quick look at the method, seems like this is the place where the validator is being instantiated, and it seems like it is not that simple, since there are some if conditions. Let's see what we have on our plate now.

  1. Seems like first there is a check if $this->validator is already instantiated. If it is, just return the instance, no need to re-create it again.
  2. The $factory is the Laravel's Validator Factory, which know how the validator instance is being created. So this is needed to create the instance.
  3. If there is a method on $this instance called validator, call that method, since Laravel will expect that we are going to create the validator on our own. In the process, is passing the $factory as argument.
  4. If there is no method called validator, just use the default validator creation process from Laravel using the createDefaultValidator method.
  5. After the validator is created, check if it the method withValidator exists. This is useful for applying extra configuration or tweaking on the created $validator instance.
  6. Then we see the method that led us here at the first place. The $validator property is being set.
  7. Just return the created instance.

I know it is obvious, but I would like to state the obvious. The $this variable is basically instance of CreateNewUserRequest, but since we extend the FormRequest, it inherits the capabilities.

Okay so, we know for a fact by just looking at the CreateNewUserRequest class, that we do not have the method validator. So we are not creating this instance on our own (even though Laravel is exposing this methods just to give more flexibility based on your needs, but we are not here for that). That means our validator instance is created using the createDefaultValidator method. But let's do quick sanity check.

If I set a xDebug breakpoint, or just simply put a dd('here') on line FormRequest:84 we are going to see that it will actually trigger this logic.

Sanity check that the `createDefaultValidator` method is being called
Sanity check that the createDefaultValidator method is being called

There we go. Let's see the createDefaultValidator method and finally see how the rules method is being called.

This seems straight forward. We have the validator factory that if you check the Laravel's Validation Documentation, you will see pretty much the same thing. In order to have a validation we need:

  1. Data that we need to validate. That is handled using the validationData method, from which we can see it is using everything that is passed as payload or query parameter calling the all() method.
  2. We need set of rules. And this is the part that should be explained better.
  3. Error messages when validation fails. Laravel is already having default message for each validation rule that they provide. In this case it is empty, since this is meant for custom messages for this validation only. You can override this method in your request class if you need some extra flexibility. The default ones are gonna be used by default from resources/lang/en/validation.php. You can read more here.
  4. Custom attributes names. If you have a attribute called really_long_and_non_meaningful_name, you can provide a meaningful name for this attribute here, when the validation fails and maybe call it Nice attribute instead. You can override this method in your request class if you need some extra flexibility. This is empty since by default we do not need any custom attribute names. You can read more here.

We have all the cards on the table now, and we know what is going on here. We can focus on how the rules method is being called right now. We can notice that the rules method is being called using the $this->container->call() method. That means Laravel's IoC Container (or Service Container) will decide how to make the call to this method. If you need some explanation on Dependency Injection and how the IoC Containers work behind the scenes, you can also check my other post where I go into details on this subject.

Let's do quick sanity check. Let's put dd(this->container->call([$this, 'rules'])); on line FormRequest:104, and check the output. According to what we saw, this should return the array of validation rules defined inside [email protected].

Result of the `this->container->call([$this, 'rules'])` call
Result of the this->container->call([$this, 'rules']) call

So that is right. We see exactly what we expected. So why this is the case, why calling the method using the container, where you can simply do $this->rules() and do the trick? It will give us the exactly same result.

Using the container vs direct call on the rules() method

If we repeat the sanity check from above, and instead of dd($this->container->call([$this, 'rules'])); we simply put dd($this->rules()) the outcome is going to be 100% the same. The rules are going to be returned, and nothing will change. So what is so special about the $this->container->call() method?

To explain that, let's have a quick example with a small problem that should be solved. Let's say we have config file config/settings.php. In this file we just define some rules about our application, and for example we define that the only business types values we want to store to our database are private and company. We would like to validate for this values as well. But since having this in one place, it is easy, since you might hardcode this rules and not having the need of the config, but if this defined settings should be used in multiple request classes, than it is a problem, and the best way would be to use the config values as source of truth. So I am assuming as a Laravel user, first thing that will come in mind is to do the following:

And then the request

Validating the `business_type` field
Validating the business_type field

And that works just great. But there is one problem with this approach. To be honest, I really... let's say I am not a fan of the Laravel's global state. I think it is great to help beginners start building something awesome, learn, or just make fast MVPs. But I wrote previously about Dependency Injection, and now I am just using global functions. Not cool.

Let's see how can we improve this, and explain the $this->container->call() method that is used to get the rules to validate the request.

Much better. So, using the container to call the rules() method from our CreateNewUserRequest instance, is allowing us to easily use Dependency Injection on the rules() method. If for some reason, Laravel's form request was using $this->rules() instead of $this->container->call([$this, 'rules']);, then this wouldn't be possible. And this is giving great flexibility while validating your request. In this example I am using Illuminate\Contracts\Config\Repository as ConfigRepository for injection, but you can find the Laravel's contracts reference here, if you are not sure which interface you should inject. Or even check the facades reference here, but keep in mind that these are the concrete classes, you should check which interface are they implementing, if any. And of course, this also applies for your own classes or interfaces, you can inject anything that is resolvable by the container.

Okay so, quick recap before we continue.

  1. We define the validation rules in [email protected] method
  2. We inject the CreateNewUserRequest into [email protected] method
  3. CreateNewUserRequest is extending FormRequest
  4. FormRequest is extending the main Laravel's request object Illuminate\Http\Request
  5. FormRequest holds the logic to figure it out how it will create the validator (by calling child's class validator() method if defined, or to create the default validator)
  6. FormRequest while creating the validator will use the container to call the [email protected] or any other class that is extending the FormRequest
  7. Since the container is responsible for calling the rules() methid, Dependency Injection is also supported.

But let's see how the request validation is triggered without us having to do that manually. We saw that our only job is to inject the request class into the controller's method, and that's it. The request will be validated while the request class is being resolved, and the controller method will not even be triggered until the validation passes.

The magic behind the automatic request validation

In order to find out the logic responsible for request validation when the request class is being resolved, we are going to go through some of the Laravel's Service Providers, or to be more specific Illuminate\Foundation\Providers\FormRequestServiceProvider

Let's start with what is familiar for us, the FormRequest class. We can see that inside the $this->app->resolving method, it is defined that when the class name Illuminate\Foundation\Http\FormRequest is resolving (since our request class is extending FormRequest), the instance for this class name should be a FormRequest class, which is created using the already existing incoming request instance (this is resolved before the resolving method is called for FormRequest). After the FormRequest class is instantiated, container and redirector are passed to the instance using Dependency Injection via setter method. We already know one of the use case why the container is needed (calling the rules() method).

So that seems straight forward, creating the instance, injecting some required instances, and that is our FormRequest instance. But then, we never mentioned before the Illuminate\Contracts\Validation\ValidatesWhenResolved interface, and we see that something is being done here with it. So let's see how this interface is actually used.

When we check the ValidatesWhenResolved interface, there is only one method defined called validateResolved. If we check the FormRequest we are going to notice that it actually implements the ValidatesWhenResolved interface, and if we try to find the validateResolved method on the FormRequest, we are going to find the implementation for this inside the trait Illuminate\Validation\ValidatesWhenResolvedTrait that is also being used in the FormRequest

And the trait's logic

  1. prepareForValidation acts like a before validation hook, we can extend this method for example on CreateNewUserRequest if needed
  2. Just like the rules() method, passesAuthorization will try to call authorize() method on your request class, if this method is defined. You can check the usage here.
  3. If the method authorize() exists, and return false, then the failedAuthorization will throw exception which results in 403 Forbidden HTTP response
  4. $instance is the validator, that we explained above how it is instantiated. So it will take all data from the request and the rules defined in our request class, and check if the validation is correct.
  5. If the validation fails, failedException will throw exception that will result in 422 Unprocessable Entity HTTP response.
  6. Similar to prepareForValidation, passedValidation acts like after validation hook.

Request validation Lifecycle

So just to sum it up. We define our route and the controller method that is responsible to handle the request. On our controller method, we inject the request class where we have defined our rules and authorization. We are now set to handle the request. When we have incoming request, Laravel's router will try to resolve the controller's method dependencies. One of the dependency is our request class, which when is resolved, based on the after resolving rule that is set in the FormRequestServiceProvider for the ValidatesWhenResolved interface, that is implemented by FormRequest which is extended by our request class, it will call the validateResolved method which is located in the ValidatesWhenResolvedTrait and used in FormRequest as well. The validateResolved method will call the methods to try and authorize and validate the request. If any of this fails, it will results in exception. If the validation passes, then rest of the dependencies (if any) are going to be resolved from the controller's method, and the controller's method will eventually be executed. And that's basically it.

Summary

Please keep in mind that this is not the only method to validate request in Laravel. Always refer to the Laravel's documentation and use whatever suits the best in your case. I find this request validation method really useful, and in my opinion, when broken down to tiny details it seems really simple, but it gives huge benefits while building your app. And yes, there is no magic into the request validation (or any other feature in general), only a lot of hours and effort to give that feel, since is really simple and easy to use.