If you have ever tried to figure out the best practices for validation in Laravel, you’ll know that it’s a topic of much discussion and little agreement. Validate in the controller? In a service layer? In the model? In a custom validation wrapper? In Javascript (NO JUST KIDDING THAT’S NEVER OK)?
Laravel’s new Form Request feature provides both standardization (“best practice” ish) and also convenience (this is more powerful and convenient then all prior options) to the process of validating and authenticating in Laravel.
NOTE: In this post I'm using the new view()
helper instead of View::make()
.
Laravel 5.0 introduces Form Requests, which are a special type of class devoted to validating and authorizing form submissions. Each class contains at least a rules()
method which returns an array of rules and anauthorize()
method which returns a boolean of whether or not the user is authorized to perform their request.
Laravel then automatically passes the user's input into the request before parse through the POST route, meaning our validation can now be moved entirely into FormRequest objects and out of our controllers and models.
If you don't have one yet, create a 5.0 project using the following command:
$ composer create-project laravel/laravel my-awesome-laravel-4-3-project-omg dev-develop --prefer-dist
Let’s imagine we’re going to be allowing a user to add a friend to our contact manager.
app/Http/routes.php
<?phpRoute::get('/', 'FriendsController@getAddFriend');Route::post('/', 'FriendsController@postAddFriend');
app/Http/Controllers/FriendsController:
<?php namespace App\Http\Controllers;use App\Http\Requests\FriendFormRequest;use Illuminate\Routing\Controller;use Response;use View;class FriendsController extends Controller{ public function getAddFriend() { return view('friends.add'); } public function postAddFriend(FriendFormRequest $request) { return Response::make('Friend added!'); }}
resources/views/friends/add.blade.php
<html><body> @foreach ($errors->all() as $error) <p class="error">{{ $error }}</p> @endforeach <form method="post"> <label>First name</label><input name="first_name"><br> <label>Email address</label><input name="email_address"><br> <input type="submit"> </form></body></html>
app/http/requests/FriendFormRequest.php
<?php namespace App\Http\Requests;use Illuminate\Foundation\Http\FormRequest;use Response;class FriendFormRequest extends FormRequest{ public function rules() { return [ 'first_name' => 'required', 'email_address' => 'required|email' ]; } public function authorize() { // Only allow logged in users // return \Auth::check(); // Allows all users in return true; } // OPTIONAL OVERRIDE public function forbiddenResponse() { // Optionally, send a custom response on authorize failure // (default is to just redirect to initial page with errors) // // Can return a response, a view, a redirect, or whatever else return Response::make('Permission denied foo!', 403); } // OPTIONAL OVERRIDE public function response() { // If you want to customize what happens on a failed validation, // override this method. // See what it does natively here: // https://github.com/laravel/framework/blob/master/src/Illuminate/Foundation/Http/FormRequest.php }}
Now, spin up a server with php artisan serve
or your favorite method. Submit the form and you can see our validation rules working without adding a line of validation logic to our controllers.
What about if we have different rules based on add vs. edit? What if we have conditional authorization based on the input? Here are a few examples, although we haven't yet established "best practices" on all of these.
There's nothing stopping you from having two (or more) separate form request classes for add and edit. You could create FriendFormRequest
with all the rules, and then extend it to make addFriendFormRequest
oreditFriendFormRequest
or whatever else, and each child class can modify the default behavior.
The benefit of rules()
being a function instead of just a property is that you can perform logic in rules().
<?php...class UserFormRequest extends FormRequest{ ... protected $rules = [ 'email_address' => 'required', 'password' => 'required|min:8', ]; public function rules() { $rules = $this->rules; if ($someTestVariableShowingThisIsLoginInsteadOfSignup) { $rules['password'] = 'min:8'; } return $rules; }}
You can also perform logic in authorize. For example:
<?php...class FriendFormRequest extends FormRequest{ ... public function authorize() { if ( ! Auth::check() ) { return false; } $thingBeingEdited = Thing::find(Input::get('thingId')); if ( ! $thingBeingEdited || $thingBeingEdited->owner != Auth::id()) { return false; } return true; }}
Or, if you want a greater level of control for all of this, you can actually overwrite the method that provides the Validator instance. I will be expanding this section of this blog post shortly.
<?php...class FriendFormRequest extends FormRequest{ public function validator(ValidationService $service) { $validator = $service->getValidator($this->input()); // Optionally customize this version using new ->after() $validator->after(function() use ($validator)) { // Do more validation $validator->errors()->add('field', 'new error'); } }}