Laravel Form Requests - more than validation
Laravel form requests are one of the most underrated features, maybe even a bit "hidden" inside the Laravel framework.
Don't just take my word for granted - here's what Taylor Otwell thinks about it:
Truth 👌 https://t.co/dug8Gxi1u6
— Taylor Otwell 🏝 (@taylorotwell) October 7, 2019
Let me show you what form requests can do for you - and how you can make use of them to write beautiful, expressive APIs.
What are form requests? #
Laravels form request classes have been around since the release of Laravel 5.0 - so they've been around since February 2015! When we take a look at the documentation of Laravel 5.0 you can see the definition of form request classes as this:
Form requests are custom request classes that contain validation logic.
Alright, so the official documentation says that form requests are meant to be used with validation logic. Let's take a look at how a bare-bone form request class looks like, when we generate it using php artisan make:request
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
class ExampleRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*
* @return bool
*/
public function authorize()
{
return false;
}
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules()
{
return [
//
];
}
}
Validation with form requests #
So out of the box, we get two methods that we can fill with our own implementation logic.
The authorize
methods that holds all logic related to the information if the request can be performed. In here you could check for access rights / guards of your current user and return true or false. Returning false will result in a 403 Forbidden response.
And the rules
method where we can define custom validation rules that should get applied to the request parameters in this specific request. This can be very handy when you want to encapsulate your validation logic into their own form request classes.
To actually use a form request class, all you need to do is type-hint it in your controller method like this:
class StoreBlogPostController {
public function __invoke(BlogPostRequest $request) {
Post::create($request->validated());
}
}
This looks really simple but it does a lot of cool things under the hood. By using method injection in our controller method, Laravel knows that the request needs to be authorized. So before the controller method gets called, Laravel is looking at the authorize
result of your form request class. If it returns false, the user gets a 403 response.
When the authorize
method returns true, Laravel will look at your rules
and automatically validates the request data against this set of rules. If one or multiple of them do not pass, Laravel is going to redirect the user back to the previous page with an $errors
message bag that you can use to display validation errors.
If everything goes through - so the authorize
method returns true and all rules
can be validated - finally your controller method gets called.
This encapsulation is really nice, because you do not need to worry about the logic inside of your controller. Once the controller gets executed, you can be sure that your data is valid and the user is authorized.
Now since the Laravel documentation mentions the primary use case for form requests is validation, are we done yet? No - not at all! Let me show you how else you can use form request classes.
Beyond validation #
Since form requests are just custom classes, they allow you to add additional logic to your requests. This allows us to create very expressive and beautiful APIs for our applications. Let me give you an example, of how I'm personally using form requests in my projects.
This request is taken from our video course and product platform and is being used when a payment webhook from Paddle, the service we're using to sell the courses and Tinkerwell, comes in:
namespace App\Http\Requests;
use App\Packages\CourseRepository;
use App\Packages\Package;
use Illuminate\Foundation\Http\FormRequest;
class PaddlePurchaseRequest extends FormRequest
{
public function authorize()
{
return true;
}
public function rules()
{
return [];
}
public function getPackage(): ?Package
{
return CourseRepository::findPackageForPaddleIdle($this->product_id);
}
public function isBlackFridayBundle(): bool
{
return $this->product_id == config('courses.blackfriday.bundle_id');
}
public function isVideoCourse(): bool
{
return ! is_null(CourseRepository::findPackageForPaddleIdle($this->product_id));
}
public function isTinkerwell(): bool
{
return is_null(CourseRepository::findPackageForPaddleIdle($this->product_id));
}
}
Woah - no validation is in here? As you can see, I am not using the form request for the "intended" purpose - or at least not for the purpose that is mentioned in the Laravel documentation. Instead I'm solely using the form request class to attach additional methods on it, so that I can use them in my controller.
To understand what this request is doing, let me quickly explain to you how the Paddle webhook works. In my video course platform, I have multiple "Packages" - such as the "Basic" and "Pro" package of my PHP Package Development video course. These packages have a Paddle-ID associated with them, so that I can identify which product was bought and can then send out my own welcome message to the user, add the relation to the database, etc.
So in the PaddlePurchaseRequest
class I added some methods that allow me to retrieve the Package class that was bought. This allows me to simply call $request->getPackage()
in my controller. Or in the case of a Tinkerwell license that was bought, I can check against that using the $request->isTinkerwell()
method.
I like to add methods to my request classes that perform logic against the data in the current request. This could be the data that was actively sent, for example within a POST request, but it could also just be the accessed domain, the referer of the request or the currently logged in user.
When you think about it, this makes a lot of sense since you abstract the logic that belongs to "resolving something from the current request" to these specific request classes.
You can even take it a step further and place common request methods that you need inside of abstract form request classes that you extend from.
Take a look at what Laravel Nova does in their form request classes. This is from the dashboard request:
namespace Laravel\Nova\Http\Requests;
use Laravel\Nova\Nova;
class DashboardCardRequest extends NovaRequest
{
/**
* Get all of the possible cards for the request.
*
* @param string $dashboard
*
* @return \Illuminate\Support\Collection
*/
public function availableCards($dashboard)
{
if ($dashboard === 'main') {
return collect(Nova::$defaultDashboardCards)
->unique()
->filter
->authorize($this)
->values();
}
return Nova::availableDashboardCardsForDashboard($dashboard, $this);
}
}
Nova not only uses form requests exactly like I mentioned - in the DashboardCardRequest
class, we have a method called availableCards
that returns all dashboard cards that should be visible to the user within the current request. But Nova also extends all their form request classes from an abstract NovaRequest
class that holds additional logic, such as returning the model and the Nova resource that was being accessed in this request.
Where to go from here? #
Form requests can be a great way to encapsulate request specific logic into their own classes. Don't get too restricted in their usage to only validate or authorize requests, but treat them as what they are: classes that allow you to extend the way you work with a specific type of request.
I'm sure that, when you work on your projects form request classes the next time, you will find plenty of ways on how you can move logic to your form requests and like this create simpler, more beautiful APIs within your applications.
If you find this kind of optimizations of your codebase interesting, I'm happy to let you know that I am currently working on a new video course about the topic of writing more elegant code, using tips and tricks like this. You can stay in the loop about the course and register for the newsletter below: