Laravel Form Requests

post-thumb

In Laravel, validating forms is key to maintaining the safety and accuracy of user data. The FormRequest component simplifies this by keeping the validation rules in a dedicated class, rather than in the controller. This also makes the code neater and allows for special functions related to the request, like fetching a model or changing a data type of a field.

FormRequest

To set up a FormRequest for our login page, execute:

1php artisan make:request LoginRequest

Next, update the generated class as follows:

 1class LoginRequest extends FormRequest
 2{
 3    public function rules()
 4    {
 5        return [
 6            'email' => 'required|string',
 7            'password' => 'required|string',
 8        ];
 9    }
10}

To use it, just add it to the appropriate method in your controller:

1class LoginController extends Controller
2{
3	public function store(LoginRequest $request)
4	{
5	    // Your code here
6	}
7}

It’s as straightforward as that!

Next, we’ll explore some of its additional features.

Method: validated

If you want to access all the keys and values set in the rules method, there’s an easy way to do it.

Instead of individually retrieving them like this:

1Post::create([
2	'title' => $request->title,
3	'body' => $request->body,
4	'published_at' => $request->published_at,
5]);

You can simply use the validated() method:

1Post::create($request->validated());

Method: afterValidation

The FormRequest class allows for adjustments on-the-fly, helping maintain the cleanliness of other classes. Consider handling an array of tags:

In the rules:

1public function rules()
2{
3    return [
4        'title' => 'required|string',
5        'body' => 'required|string',
6        'published_at' => 'datetime',
7    ];
8}

Syncing tags with our post traditionally looks like this:

1public function store(StorePostRequest $request) {
2	$post = Post::create($request->only('title', 'body','published_at'));
3    
4	if($request->has('tags')) {
5		$post->tags()->attach(explode(',', $request->tags));
6	}
7}

Notice how we split the tags to attach each one to the post? We can handle this directly within the FormRequest using the afterValidation method:

 1class StorePostRequest extends FormRequest
 2{
 3    public function rules(): array
 4    {
 5        return [
 6            'title' => 'required|string',
 7            'body' => 'required|string',
 8            'published_at' => 'datetime',
 9        ];
10    }
11
12    protected function passedValidation() {
13        $this->merge([
14            'tags' => $this->tags ? explode(',', $this->tags) : []
15        ]);
16    }
17}

With this adjustment, the tags will already be in an array format after validation. This simplifies the controller code:

1public function store(StorePostRequest $request) {
2    $post = Post::create($request->only('title', 'body','published_at'));
3
4    $post->tags()->attach($request->tags);
5}

Method: prepareForValidation

Using the prepareForValidation method, we can adjust our input just before the FormRequest begins its validation process. For our tags, we want to ensure they are integers.

First, let’s tweak the tags input:

1protected function prepareForValidation() {
2    $this->merge([
3        'tags' => $this->tags ? explode(',', $this->tags) : []
4    ]);
5}

Now, the updated FormRequest class should be:

 1class StorePostRequest extends FormRequest
 2{
 3    public function rules(): array
 4    {
 5        return [
 6            'title' => 'required|string',
 7            'body' => 'required|string',
 8            'published_at' => 'datetime',
 9            'tags' => 'array',
10            'tags.*' => 'integer',
11        ];
12    }
13
14    protected function prepareForValidation() {
15        $this->merge([
16            'tags' => $this->tags ? explode(',', $this->tags) : []
17        ]);
18    }
19}

The controller, despite these changes, will still treat the tags as an array:

1public function store(StorePostRequest $request) {
2    $post = Post::create($request->only('title', 'body','published_at'));
3
4    $post->tags()->attach($request->tags);
5}

Method: user

You can directly access the authenticated user from the request class:

1public function store(StorePostRequest $request) {
2    $post = $request->user()->posts()->create(
3	    $request->only('title', 'body','published_at')
4    );
5}

This method retrieves the currently authenticated user, making it easier to associate new posts with the user.

Understanding Validation

When you use a FormRequest in the method signature, Laravel automatically handles the validation for you. That means you don’t need to manually call $request->validate() in the controller. Let’s understand how this magic happens.

To dissect the validation component, a good starting point is to search for instances where the rules method from our StorePostRequest class is used.

You might be surprised to find that PHPStorm’s “find usages” feature doesn’t detect any use of this method directly in our custom FormRequest class. But, if you search within its parent class, FormRequest, you’ll discover it’s utilized in the createDefaultValidator method.

 1protected function createDefaultValidator(ValidationFactory $factory)
 2{
 3    $rules = method_exists($this, 'rules') ? $this->container->call([$this, 'rules']) : [];
 4
 5    $validator = $factory->make(
 6        $this->validationData(), $rules,
 7        $this->messages(), $this->attributes()
 8    )->stopOnFirstFailure($this->stopOnFirstFailure);
 9
10    if ($this->isPrecognitive()) {
11        $validator->setRules(
12            $this->filterPrecognitiveRules($validator->getRulesWithoutPlaceholders())
13        );
14    }
15
16    return $validator;
17}

The FormRequest class utilizes Laravel’s service container when invoking the rules method. This approach is beneficial as it allows for dependency injection directly into the rules method, meaning any service or class you need can be automatically resolved and injected.

Now Let’s see the usage of createDefaultValidator

 1protected function getValidatorInstance()
 2{
 3    if ($this->validator) {
 4        return $this->validator;
 5    }
 6
 7    $factory = $this->container->make(ValidationFactory::class);
 8
 9    if (method_exists($this, 'validator')) {
10        $validator = $this->container->call([$this, 'validator'], compact('factory'));
11    } else {
12        $validator = $this->createDefaultValidator($factory);
13    }
14
15    if (method_exists($this, 'withValidator')) {
16        $this->withValidator($validator);
17    }
18
19    if (method_exists($this, 'after')) {
20        $validator->after($this->container->call(
21            $this->after(...),
22            ['validator' => $validator]
23        ));
24    }
25
26    $this->setValidator($validator);
27
28    return $this->validator;
29}

Laravel follows a series of steps when validating using the FormRequest. Initially, Laravel checks if your FormRequest has its own Validator class; if not, it uses the createDefaultValidator. Then, Laravel examines if there are any after hooks to run.

Continuing our exploration, we see that getValidatorInstance is invoked.

Next, we have the ValidatesWhenResolvedTrait. This trait is applied to all FormRequests and houses methods for validating input data when the FormRequest class is determined.

 1public function validateResolved()
 2{
 3    $this->prepareForValidation();
 4
 5    if (! $this->passesAuthorization()) {
 6        $this->failedAuthorization();
 7    }
 8
 9    $instance = $this->getValidatorInstance();
10
11    if ($this->isPrecognitive()) {
12        $instance->after(Precognition::afterValidationHook($this));
13    }
14
15    if ($instance->fails()) {
16        $this->failedValidation($instance);
17    }
18
19    $this->passedValidation();
20}

Here, we encounter the prepareForValidation and passedValidation methods we discussed earlier.

Moving on, let’s dive into how the validateResolved() method is used.

 1namespace Illuminate\Foundation\Providers;
 2
 3class FormRequestServiceProvider extends ServiceProvider
 4{
 5    public function boot()
 6    {
 7        $this->app->afterResolving(ValidatesWhenResolved::class, function ($resolved) {
 8            $resolved->validateResolved();
 9        });
10
11        $this->app->resolving(FormRequest::class, function ($request, $app) {
12            $request = FormRequest::createFrom($app['request'], $request);
13
14            $request->setContainer($app)->setRedirector($app->make(Redirector::class));
15        });
16    }
17}

Indeed, in the FormRequestServiceProvider, Laravel sets up an afterResolving hook that triggers the ValidatesWhenResolved trait to invoke the validateResolved method, which effectively performs the validation.

And that’s the fundamental process of how FormRequest validation is executed when it’s incorporated into your controller’s method.

Happy Coding!

comments powered by Disqus

You May Also Like