Manipulating request data before performing validation in Laravel
When using form request classes, it can be really handy to be able to manipulate the request data before running any of the validation rules. This could be to:
- Coerce the data into a format the validation is expecting (e.g. convert a list of comma separated values to an array).
- Cast a value to another type (e.g. string to integer or string to boolean)
- Account for common user errors, like typos.
- Remove inappropriate or potentially malicious content from input data.
Example form request
Let's start with an example form request for storing a blog post. Take notice of the rules that are applied the inputs.
<?php
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
class StorePostRequest extends FormRequest
{
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules()
{
return [
'title' => 'required|max:200',
'body' => 'required',
'tags' => 'required|array|max:10',
'is_published' => 'required|boolean',
'author_name' => 'required',
];
}
}
Now let's assume that a user has submitted a form to create a post and has passed the following input data:
[
'title' => 'My bolg post',
'body' => 'This is the <script>alert('Evil!')</script> post body.',
'tags' => 'laravel,code,updates',
'is_published' => 'true',
'author_name' => 'Ben Sampson',
]
The tags
are sent from the front end as a list of comma separated values and the is_published
input is interpreted as a string. Both of these will fail validation despite being correct from a content perspective, just in the wrong format.
Additionally, in a terrible spell of bad luck, it looks like the title
contains a typo and the body
contains some malicious code. While technically it could be said that these aren't validation issues, we can still help to save the user from themselves and protect our application.
The prepareForValidation
method
Taking a look at the base Illuminate\Foundation\Http\FormRequest
class, which all form requests extend, we can see that it uses a trait called ValidatesWhenResolvedTrait
. This trait contains the method we're looking for which allows us to tap into the request data before any validation begins. The method is appropriately named prepareForValidation
and has no default actions, meaning it's been put there for the sole purpose of being overridden.
protected function prepareForValidation()
{
// no default action
}
Manipulating the request data
Within our form request, we can make use of the prepareForValidation
method to manipulate any of the request input values.
Because the base FormRequest
class extends the Request
class we have access to the merge
helper method which we can use to update just the input values that we need to. We can also access the input values themselves like properties on the class.
<?php
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
class StorePostRequest extends FormRequest
{
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules()
{
return [
'title' => 'required|max:200',
'body' => 'required',
'tags' => 'required|array|max:10',
'is_published' => 'required|boolean',
'author_name' => 'required',
];
}
/**
* Prepare the data for validation.
*
* @return void
*/
protected function prepareForValidation()
{
$this->merge([
'title' => fix_typos($this->title),
'body' => filter_malicious_content($this->body),
'tags' => convert_comma_separated_values_to_array($this->tags),
'is_published' => (bool) $this->is_published,
]);
}
}
Using the magic helper methods that exist elsewhere in our application (🤫 they don't), we've managed to manipulate the data such that it now looks like the following before validation is performed:
[
'title' => 'My blog post', // ✅ Typo fixed!
'body' => 'This is the post body.', // ✅ Malicious content removed!
'tags' => ['laravel', 'code', 'updates'], // ✅ Now an array!
'is_published' => true, // ✅ Now a boolean!
'author_name' => 'Ben Sampson', // ✅ Still the same
]
The values are updated on the request itself, so however we access them in our controller after performing validation, we'll be getting the manipulated values back.