Sep 10, 2014 | laravel, 5.0, laravel 5

Laravel 5.0 - Form Requests

Series

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

!
Warning: This post is over a year old. I don't always update old posts with new information, so some of this information may be out of date.

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


This is part of a series of posts on New Features in Laravel 5.0:

  1. Sep 10, 2014 | laravel, 5.0, laravel 5
  2. Sep 10, 2014 | laravel, 5.0, laravel 5
  3. Sep 12, 2014 | laravel, laravel 5, 5.0
  4. Sep 20, 2014 | laravel, 5.0, laravel 5
  5. Sep 28, 2014 | laravel, laravel 5, 5.0
  6. Sep 30, 2014 | laravel, 5.0, laravel 5
  7. Oct 9, 2014 | laravel, 5.0, laravel 5
  8. Oct 10, 2014 | laravel, 5.0, laravel 5
  9. Oct 10, 2014 | laravel, 5.0, laravel 5
  10. Nov 20, 2014 | laravel, 5.0, laravel 5
  11. Jan 2, 2015 | laravel, 5.0, commands, laravel 5
  12. Jan 16, 2015 | laravel, laravel 5
  13. Jan 19, 2015 | laravel 5, laravel
  14. Jan 21, 2015 | laravel, events, 5.0, laravel 5
  15. Jan 26, 2015 | laravel, laravel 5
  16. Feb 1, 2015 | laravel, laravel 5
  17. Feb 14, 2015 | laravel 5, laravel, eloquent

Subscribe

For quick links to fresh content, and for more thoughts that don't make it to the blog.