Improve Your Code with Form Requests in Laravel

Jasper Briers
7 min readSep 2, 2020

--

In this blogpost I’m going to show you how to merge validation of 4 different type of requests on the same model into one FormRequest class.

When you start a project in Laravel, soon you most likely will be handling form validation. The framework provides different ways of dealing with validation, for example by adding the validation rules inside your controller:

namespace App\Http\Controllers; class PostController extends Controller
{
public function store(Request $request)
{
$validatedData = $request->validate([
'title' => 'required|unique:posts|max:255',
'body' => 'required',
'date' => 'sometimes|date',
]);

// The blog post is valid...
}
}

There is nothing wrong with this approach. You are sure that you only deal with validated data, and it is clear by looking at the controller what you can expect from the $ValidatedData object.

But writing the validation logic in the controller will break The Single Responsibility Principle. Having many responsibilities in a single class makes it more difficult to handle new requirements over time. Even in this controller it could be possible that you need a method to update the blog post. You would probably copy the rules from the store method to the update method. And imagine that you need to add an API later on. You would create a separate controller handling the API request, while the validation rules stay the same.

Form Requests to the rescue

Form requests in Laravel allows us to extract the validation logic to a dedicated class. Creating a form request is possible with a simple Artisan CLI command as shown in the Laravel documentation:

php artisan make:request StoreBlogPost

This however creates a StoreBlogPost form request, which as the name implies would be used for storing new blog posts. This would mean that we need to create another UpdateBlogPost form request, which would duplicate (most) of the validation rules. It would make more sense to have one form request which will validate both the store (create) and update (edit) requests.

So the actual command we are going to use is:

php artisan make:request BlogPostRequest

This will create the BlogPostRequest class inside the app/Http/Requests folder. Notice that we append “Request” to the class name, which makes it more clear that we have a form request instead of a model for example.

Inside the controller we find two methods: authorize() and rules().
With the authorize() method it is possible to handle authorisation for the store and update request. But this leaves us handling authorisation of other requests on the controller (such as the index or show request) inside the controller. Therefore authorisation is better handled within another dedicated class, for example with resource policies. When handling authorisation outside the form request, we can just remove the authorize() method inside the form request class.

We can now copy the rules array from the controller to the form request, which results in this code:

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

Now we need to tell our controller to use this form request. Luckily this is made very simple by just changing the type-hinted $request variable in the method signature:

namespace App\Http\Controllers;use App\Http\Requests\BlogPostRequest;class PostController extends Controller
{
public function store(BlogPostRequest $request)
{
$validatedData = $request->validated();

// The new blog post is valid...
}
}

In our controller, we call the $request->validated() method which receives all validated input. We do not need to worry about validation in our controller anymore, and we are sure that we only receive input from fields that are defined inside the form request.

Array of rules

In our form request, all rules for each field are within a single | delimited string. But when we are going to use these rules for validation of the update request, we need to make a small modification. We now have a rule unique:posts which allows us to have a unique title for each blog post. But when we want to update for example the body of the blog post, the current rule on the title would produce an error. Changing the unique rule means we have to change the whole ruleset of the title field, which is rather pointless for the other rules on this field. Therefore it is better to use an array of rules:

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

The same Form Request for update requests

Our form request is now able to validate input for storing new blog posts, but for updating the blog post a small modification is necessary. We need to tell the unique validation rule that the current title can be ignored while checking that the title is unique.

First of all, we are going to change the 'unique:posts' rule and define the rule fluently like this: Rule::unique('posts'). This is fine for the store method, but for the update method, we need to add the ignore method like this: Rule::unique('posts')->ignore($this->post->id);

By the way, the $this->post comes from the controller where $post is type hinted in the updated method signature:

namespace App\Http\Controllers;use App\Http\Requests\BlogPostRequest;
use App\Models\Post;
class PostController extends Controller
{
//
public function update(BlogPostRequest $request, Post $post)
{
$validatedData = $request->validated();

// The updated blog post is valid...
}
}

So we have two different kinds of unique rules for the title field: one for the store and another for the update action. We know that a store request is always done with a POST request method, while an update request uses a PUT or PATCH method. This allows us to adapt the rule depending on the current request method:

namespace App\Http\Requests;

use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Validation\Rule;

class BlogPostRequest extends FormRequest
{
public function rules()
{
$rule_title_unique = Rule::unique('posts', 'name');
if ($this->method() !== 'POST') {
$rule_title_unique->ignore($this->post->id);
}

return [
'title' => ['required', $rule_title_unique, 'max:255'],
'body' => ['required'],
'date' => ['sometimes', 'date'],
];
}
}

To break it down, we assign the rule to a variable. If the current request method is not POST, we know it is an update request so we add
->ignore($this->post-id) to the rule. The variable is then used inside the array with rules that will be used to validate the request.

Preparing the input for validation

Let’s say we have a date field in our form where users enter a date in the form of d/m/Y while the date field in our database is in the format Y-m-d. For this we can use the prepareForValidation method. It would also be possible to modify our data after validation, but as we will see later on it makes more sense to format the data before validation, preferably in the format used in our database.

namespace App\Http\Requests;

use Carbon\Carbon;

class BlogPostRequest extends FormRequest
{
//

protected function prepareForValidation()
{
$this->merge([
'date' => Carbon::createFromFormat(
'd/m/Y', $this->date
)->toDateString(),
]);
}
}

The Carbon toDateString() method in the example above gives us a date in the Y-m-d format.

With this our form request is complete. We now have a separate class with one rule set which we can use for handling both storing and updating blog posts via web requests.

The same Form Request for API requests

When your project is going to add support for API requests next to web requests, you probably going to add a new controller for handling these API requests. Luckily we have abstracted our rules, so we can just reuse our form request class in our API controller:

namespace App\Http\Api\Controllers;use App\Http\Requests\BlogPostRequest;
use App\Models\Post;
class PostController extends Controller
{
public function store(BlogPostRequest $request)
{
$validatedData = $request->validated();

// The new blog post is valid
}
public function update(BlogPostRequest $request, Post $post)
{
$validatedData = $request->validated();

// The updated blog post is valid
}
}

This API controller is almost identical as the controller we had before, but the responses from the API controller will be most likely JSON formatted, while the web controller will answer the request with a view. So a separate controller for the API requests makes sense.

We are reusing our BlogPostRequest, but in our API we expect the date field in requests always in the Y-m-d format, as it makes more sense for API requests to work with this format. This means we don’t need to modify the input from the API request as we do now in the prepareForValidation method of our form request. We could add another form request to handle the API requests without the prepareForValidation method, but then again we have just a copy of our validation rules, which isn’t very maintainable.

Most likely your API endpoints are prefixed with api/, so we can use this information to handle API requests inside our form request like this:

namespace App\Http\Requests;

use Carbon\Carbon;

class BlogPostRequest extends FormRequest
{
//

protected function prepareForValidation()
{
if (request()->is('api/*')) {
return;
}
$this->merge([
'date' => Carbon::createFromFormat(
'd/m/Y', $this->date
)->toDateString(),
]);
}
}

We now check for an API request inside the prepareForValidation method, and exit early when an API request is detected. This way the date field input will only be modified for web requests. This is also the reason why we change the input before handling the validation, we can now use the same rules to validate both the API and web requests.

With these techniques we can use one form request class containing one set of rules which handles storing and updating blog posts, both coming from a web or an API request. When the rules should be changed, we only need to modify these rules once for all four request types.

Write even more beautiful code in your controllers

Do you want to get even more out of form requests? As these are just classes, they are an excellent candidate to add more methods involving the request. You can find out more in this highly recommended blog post by Marcel Pociot:
Laravel Form Requests - more than validation

If you’ve got some thoughts to share about this method or other code improvements, you can reach me on Twitter or leave a comment below.

--

--