Laravel 5.0 - Form Requests

(This is part of a series of posts on New Features in Laravel 5.0.)

  1. Laravel 5.0 - Form Requests
  2. Laravel 5.0 - ValidatesWhenResolved
  3. Laravel 5.0 - Directory structure and namespace
  4. Laravel 5.0 - Route Caching
  5. Laravel 5.0 - Cloud File Drivers
  6. Laravel 5.0 - Method Injection
  7. Laravel 5.0 - Route Annotations (removed)
  8. Laravel 5.0 - Event Annotations (removed)
  9. Laravel 5.0 - Middleware (Filter-style)
  10. Laravel 5.0 - Environment Detection & Environment Variables
  11. Laravel 5.0 - Event Scheduling
  12. Laravel 5.0 - Commands & Handlers
  13. Upgrading from Laravel 4 to Laravel 5
  14. Bringing Whoops Back to Laravel 5
  15. Laravel 5.0 - Events & Handlers
  16. Laravel 5.0 - Generating Missing Events
  17. Laravel 5.0 - Custom Error Pages
  18. Laravel 5.0 - Eloquent Attribute Casting

Laravel 5.0 #

Laravel 5.0 is coming out in November, and there are a lot of features that have folks excited. The New Directory structure is, in my mind, a lot more in line with how most developers work; Flysystem integration will make working with files endlessly more flexible and powerful; Contracts is a great step towards making Laravel-friendly packages that aren’t Laravel-dependent; and Socialite looks about 100x easier than Opauth. Also, Method Injection opens up a lot of really exciting opportunities.

One of the most valuable aspects of Laravel for me is that it allows for rapid app development. Laravel, and other frameworks like it, automate out the repetitive work that you have to do on every project. And a lot of newer features have been focusing on this. Cashier, and now Socialite and Form Requests.

The headache of form validation #

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().

Form Requests to the rescue #

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 an authorize() 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.

Getting started: Spin up a Laravel 5.0 project #

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.

1. Add your routes #

app/Http/routes.php

<?php
Route::get('/', 'FriendsController@getAddFriend');
Route::post('/', 'FriendsController@postAddFriend');

2. Create your controller #

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!');
    }
}

3. Create your view #

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>

4. Create your FormRequest #

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.

Other use cases #

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.

Separate form requests #

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 or editFriendFormRequest or whatever else, and each child class can modify the default behavior.

Conditional logic #

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;
    }
}

Custom Validator #

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');
        });
    }
}

ValidatesWhenResolved #

I'll be writing more on this in a new blog post soon, but the concept of validating methods/routes/etc. when the IOC resolves something is now a separated to an interface: https://github.com/illuminate/contracts/blob/master/Validation/ValidatesWhenResolved.php

Other customizable parameters #

  • $redirect: the URI to redirect to if validation fails
  • $redirectRoute: the route to redirect to if validation fails
  • $redirectAction: the controller action to redirect to if validation fails
  • $dontFlash: the input keys that should not be flashed on redirect (default: ['password', 'password_confirmation'])

Abschluss #

As you can see, Form Requests are powerful and convenient ways to simplify validation and authentication for form requests. Have trouble following this? Check out the Laracast for Form Request.

Since 5.0 is still under development, these things could change, or I may have missed something. Suggestions or corrections? Hit me up on Twitter.


Comments? I'm @stauffermatt on Twitter


Tags: laravel | 5.0 | laravel 5

Matt Stauffer headshot

Hi, I'm Matt Stauffer.

I'm partner & technical director at Tighten Co.

You can find me on Twitter at @stauffermatt


Like what you're reading?

I wrote an entire 450+ page book for O'Reilly: Laravel: Up and Running.

You can order the eBook or print book today.