3. Collecting and Handling User Data

Laravel provides a collection of tools for gathering, validating, normalizing. and filtering user-provided data.

Injecting a Request Object

The common tool for accessing user data in Laravel is injecting an instance of the Illuminate\Http\Request object. It provides easy access to all of the ways users can provide input to our sites: POST, posted JSON, GET, and URL segments.

1
2
3
Route::get('form', function(Illuminate\Http\Request $request){

})

$request->all()

$request->all() returns an array containing all of the input the user has provided, from every source.

Let’s say we have a form POST to a URL with a query parameter

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<form method="post" action="/post-route?utm=12345">
{{ csrf_field() }}
<input type='text' name='firstName'>
<input type='submit'>
</form>

Route::post('post-route', function (Request $request){
var_dump($request->all());
});

// Outputs:
/**
* [ '_token' => 'your csrf token',
* 'firstName' => 'value',
* 'utm' => 12345
* ]
**/

$request->except() and $request->only()

$request->except() allows you to choose one or more fields to exclude, for example the ‘_token’ filed

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<form method="post" action="/post-route?utm=12345">
{{ csrf_field() }}
<input type='text' name='firstName'>
<input type='submit'>
</form>

Route::post('/post-route', function (Request $request){
var_dump($request->except('_token'));
});

// Outputs:
/**
* [
* 'firstName' => 'value',
* 'utm' => 12345
* ]
**/

$request->only() is the inverse of $request->except()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<form method="post" action="/post-route?utm=12345">
{{ csrf_field() }}
<input type='text' name='firstName'>
<input type='submit'>
</form>

Route::post('/post-route', function (Request $request){
var_dump($request->only(['firstName','utm']));
});

// Outputs:
/**
* [
* 'firstName' => 'value',
* 'utm' => 12345
* ]
**/

$request->has() and $request->exists()

$request->has() and $request->exists()could detect whether a particular piece of user input is available to you. The difference is that has() returns FALSE if the key exists and is empty; exists() returns TRUE if the key exists, even if it’s empty.

request->input()

request->input() allows you to get the value of just a single field.

1
2
3
Route::post('/post-route', function(Request $request){
$userName = $request->input('name','(anonymous)');
});

The second parameter is the default value, so if the user hasn’t passed in a value, you can have a sensible fallback.

Array Input

Laravel also provides convenience helpers for accessing data from array input.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<form method='post' action='/post-route'>
{{ csrf_field() }}
<input type='text' name='employees[0][firstName]'>
<input type='text' name='employees[0][lastName]'>
<input type='text' name='employees[1][firstName]'>
<input type='text' name='employees[1][lastName]'>
</form>

Route::post('/post-route', function(Request $request){
$employeeZeroFirstName = $request->input('employees.0.firstName');
$allLastNames = $request->input('employees.*.lastName');
$employeeOne = $request->input('employees.1');
})

// If forms filled out as "Jim" "Smith" "Bob" "Jones":
// $employeeZeroFirstName = 'Jim';
// $allLastNames = ['Smith', 'Jones'];
// $employeeOne = ['firstName' => 'Bob', 'lastName' => 'Jones']

From Request

Let’s say we have a URL http://www.myapp.com/users/1, $request->segments() will return an array of all segments, and $request->segment($segmentId) will return the value of a single segment. The segment has is 1-based index. So $request->segment(1) will return ‘users’

Uploaded Files

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<form method='post' enctype='multipart/form-data' action='form'>
{{ csrf_field() }}
<input type='text' name='name'>
<input type='file' name='profile_picture'>
<input type='submit'>
</form>

Route::post('form', function(Request $request){
if($request->hasFile('profile_picture') && $request->file('profile_picture')->isValid())
// isValid() is called on the file itself, it will error if the user didn’t upload a file
{
var_dump($request->file('profile_picture'));
}
});

// Output:
// UploadedFile(details)

Validation

Laravel has a few ways to validate request data.

validate() in the Controller Using ValidatesRequests

Out of the box, all Laravel controllers use the ValidatesRequests trait, which has a convenient validate() method

We have a route like this:

1
Route::post('users','UserController@store')

And the code in controller is

1
2
3
4
5
6
7
8
9
10
11
12
13
// app/Http/Controllers/UserController.php 
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
class RecipesController extends Controller {
public function store(Request $request){
$this->validate($request, [
'name' => 'required',
'password' => 'required'
]);
// $request is valid; proceed to save it.
}
}

If the data is valid, it will move on to next code. But if the data isn’t valid, it throws a ValidationException. This contains instructions to router about how to handle this exception. If request is Ajax, the exception will create a JSON response containing the validation errors. If not, the exception will return a redirect to the previous page with all user input and the validation errors for repopulatin a failed form and showing some errors.

Manual Validation

If you are not in a controller, or if for some other reason, you can manually create a Validator instance to validate request data like this

1
2
3
4
5
6
7
8
9
10
Route::post('user', function (Illuminate\Http\Request $request){
$validator = Validator::make( $request->all(),[
'name' => 'required',
'password' => 'required'
]);

if($validator->fails()){
return redirect('user/create')->withErrors($validator)->withInput();
}
});

Display Validation Error Messages

The validate() method in controllers and the withErrors() method on redirects flashes any errors to the session. These errors are made available to the view you’re being redirected to in the $errors variable. $errors variable will be available every time your load the view, even if it’s empty.

So you can display error like this

1
2
3
4
5
6
7
@if($errors->any())
<ul>
@foreach($errors->all() as $error)
<li>{{ $error}}</li>
@endforeach
</ul>
@endif

Form Requests

You may notice that there are certain patterns like vaidation rules, user authentication and authorization are repeated. So you can extract these common behviours out of controller methods.

A form request is a custom request class that is intended to map to the submission of a form, and the request takes the responsibility for validating the request, authorizing the user, and optionally redirecting the user upon a failed validation.

create form request

You can create a new form request using Artisan

1
php artisan make:request CreateCommentRequest

You now have a form request object available at app/Http/Requests/CreateCommentRequest.php

Every form request class provides either one or two public methods. The first one is rules(), which needs to return an array of validation rules for this request, and the second one is authorize(); if this returns true, the user is authorized to perform this reques, and if false, the user is rejected.

Let’s say we have a route like this:

1
Route::post('blogPosts/{blogPost}', function(){ // do something})

And a request like this

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<?php
namespace App\Http\Requests;

use App\BlogPost;
use App\Http\Requests\Request;

class CreateCommentRequest extends Request
{
public function rules()
{
return [
'body' => 'required|max:1000'
];
}

public function authorize()
{
$blogPostId = $this->route('blogPost'); //We're grabing the segment from the route named blogPost.
return auth()->check() && BlogPost::where('id', blogPostId)->where('user_id', auth()->user()->id)->exists();
}
}

using form request

Now we can user the custome request like this:

1
2
3
Route::post('blogPosts/{blogPost}', function(App\Http\Requests\CreateCommentRequest $request){ 
// do something
});

Eloquent Model Mass Assignment

It’s a common pattern to pass the entirety of a form’s input directly to a database model. In Laravel, that might look like this:

1
2
3
Route::post('/post',function(Request $request){
$newPost = Post::create($request->all());
})

It’s ok if in the request, user have all the field required to create a post, but what if they use their browser tools to add other dummy fileds or set some attribute like author_id to other person?

Eloquent has a concept called ‘mass assignment’ that allows you to either whitelist fields (by set the model’s $fillable property )that are fillable or blacklist fields that aren’t fillable.

For example we want to protect our author_id, so in our modle class, we can do something like this:

1
2
3
4
5
6
7
<?php
namespace App;
use Illuminate\Database\Eloquent\Model
class Post extens Model
{
protected $guarded = ['author_id'];
}

By setting the author_id guarded, we ensure that users will no longer be able to override the value of this field by manually adding it to the contents of a form that they are sending to our app;