Laravel 5 Form Request data pre-manipulation
Solution 1
in laravel 5.1 you can do that
<?php namespace App\Http\Requests;
class UpdateSettingsRequest extends Request {
public function authorize()
{
return true;
}
public function rules()
{
return [];
}
protected function getValidatorInstance()
{
$data = $this->all();
$data['date_of_birth'] = 'test';
$this->getInputSource()->replace($data);
/*modify data before send to validator*/
return parent::getValidatorInstance();
}
Solution 2
Since Laravel 5.4, you can use the prepareForValidation
method on FormRequest classes.
<?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,
]);
}
}
There's a more detailed write-up here: https://sampo.co.uk/blog/manipulating-request-data-before-performing-validation-in-laravel
Solution 3
Note: This question and answer were both posted before Laravel 5.1 was released and both target 5.0. For 5.1 and above, see this answer by @Julia Shestakova or this answer by @BenSampo for a more modern solution.
After some messing around myself I've come up with the following:
app/Http/Requests/Request.php
<?php namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
abstract class Request extends FormRequest {
/**
* Override the initialize method called from the constructor to give subclasses
* an opportunity to modify the input before anything happens.
*
* @param array $query
* @param array $request
* @param array $attributes
* @param array $cookies
* @param array $files
* @param array $server
* @param null $content
*/
public function initialize(array $query = array(), array $request = array(), array $attributes = array(), array $cookies = array(), array $files = array(), array $server = array(), $content = null)
{
parent::initialize($query, $request, $attributes, $cookies, $files, $server, $content);
// Grab the input
$data = $this->getInputSource()->all();
// Pass it off to modifyInput function
$data = $this->modifyInput($data);
// Replace modified data back into input.
$this->getInputSource()->replace($data);
}
/**
* Function that can be overridden to manipulate the input data before anything
* happens with it.
*
* @param array $data The original data.
* @return array The new modified data.
*/
public function modifyInput(array $data)
{
return $data;
}
}
Then on extending classes you can just override the modifyInput
method like this:
app/Http/Requests/TestRequest.php
<?php namespace App\Http\Requests;
class TestRequest extends Request {
public function authorize()
{
return true;
}
public function rules()
{
return [];
}
/**
* Modify the input.
*/
public function modifyInput(array $data)
{
$data['date_of_birth'] = 'something';
// Make sure to return it.
return $data;
}
}
This seems to work for my needs. I'm not sure of any drawbacks of doing it this way so I welcome any comments/criticism.
The answer given by The Shift Exchange above will also work just fine.
Solution 4
I took a similar approach to Julia Logvina but I think this way is a slightly more elegant way to add/modify fields before validation (Laravel 5.1)
<?php
namespace App\Http\Requests;
use App\Http\Requests\Request;
class UpdateSettingsRequest extends Request
{
/**
* Determine if the user is authorized to make this request.
*
* @return bool
*/
public function authorize()
{
return true;
}
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules()
{
return [];
}
/**
* Extend the default getValidatorInstance method
* so fields can be modified or added before validation
*
* @return \Illuminate\Contracts\Validation\Validator
*/
protected function getValidatorInstance()
{
// Add new data field before it gets sent to the validator
$this->merge(array('date_of_birth' => 'test'));
// OR: Replace ALL data fields before they're sent to the validator
// $this->replace(array('date_of_birth' => 'test'));
// Fire the parent getValidatorInstance method
return parent::getValidatorInstance();
}
}
This will extend the default getValidatorInstance
so we can modify the input values in the request before it gets to the validator (preventing it from using the original unmodified data). Once the data has been modified, it fires off the original getValidatorInstance
then everything continues as normal.
You can either use $this->replace(array())
or $this->merge(array())
your new fields into your request. I've included an example of how to do both in the snippet above.
replace()
will replace all fields with the array you provide.
merge()
will add a new field into your request.
Solution 5
I think this is the best approach to do so: Laravel 5.1 Modify input before form request validation
In Laravel 5.4+ there is a dedicated method for stuff like this so use it: prepareForValidation
Jonathon
Updated on February 13, 2020Comments
-
Jonathon over 4 years
I'm processing a form where a user can update their date of birth. The form gives the user 3 separate fields for
day
,month
andyear
. On the server-side of course I want to treat these 3 separate fields as one value i.e.yyyy-mm-dd
.So before validation and updating my database, I want to alter the form request to create a
date_of_birth
field by concatenatingyear
,month
andday
with-
characters to create the date format I need (And possibly unset the original 3 fields).Achieving this manually with my controller is not a problem. I can simply grab the input, join the fields together separated by
-
characters and unset them. I can then validate manually before passing off to a command to deal with the processing.However, I would prefer to use a
FormRequest
to deal with the validation and have that injected into my controller method. Therefore I need a way of actually modifying the form request before validation is executed.I did find the following question which is similar: Laravel 5 Request - altering data
It suggests overriding the
all
method on the form request to contain the logic for manipulating the data prior to validation.<?php namespace App\Http\Requests; class UpdateSettingsRequest extends Request { public function authorize() { return true; } public function rules() { return []; } public function all() { $data = parent::all(); $data['date_of_birth'] = 'test'; return $data; }
This is all well and good for the validation, but overriding the
all
method doesn't actually modify the data on the form request object. So when it comes to executing the command, the form request contains the original unmodified data. Unless I use the now overriddenall
method to pull the data out.I'm looking for a more concrete way to modify the data within my form request that doesn't require the calling of a specific method.
Cheers
-
Jonathon over 9 yearsThanks for your reply. Is there not a cleaner, "one time" way of doing it? It feels very dirty the data must be manipulated every time data is requested. From looking into the code it seems that the
all()
function is called every time data is requested (i.e. frominput
,except
etc.). -
Laurence over 9 yearsIts a very clean way - because it is neatly handled inside the FormRequest object. This means the controller and other aspects of your application have no idea what is occurring. The fact it occurs each time
all()
is called is a non-issue - the optimisation level you are talking about is trival. Focus on writing good clean maintainable code will have a better outcome :) -
Jonathon over 9 yearsYeah it's definitely trivial in this scenario. But in theory, if there was a lot to do here, it could be a different story. I prefer to write optimal code where I can. I would liken this approach to getting the length of an array we are looping through on every iteration. It works, the optimisation is probably trivial but it all mounts up. I've tried looking for other methods to override and have came to the conclusion that the
initialize
method found inSymfony\Component\HttpFoundation\Request
might be the way to go. Any thoughts on this? It's called from the constructor. -
Laurence over 9 yearsThe thing is - the
all()
method is already doing an array loop on every iteration anyway. If you look at the originalall()
method - this is it:return array_replace_recursive($this->input(), $this->files->all())
. So you making this modification is not having any impact on your performance. You are trying to optimizie a process that doesnt need optimizing... -
MisterBla almost 9 years+1 This is actually a very nice way of doing it, I like it. Although I'd cache the sourceInput by means of
$source = $this->getInputSource();
-
Jonathon over 7 yearsSure, in 5.4 this is no doubt the best solution to the problem. However, for versions prior to 5.4 (This question was asked in March 2015 where 5.0 was the version at that time), then the other answers given below are more appropriate, such as the one given by @Julia Logvina for 5.1, which is a long term support (LTS) version
-
Thomas Venturini over 7 yearsJep, but folks like me are still finding this if they look for the gerneral topic on 5.x so I think it is valid to mention the solution for 5.4+ ;)
-
Steve Bauman almost 7 yearsThanks @Thomas Venturini! Using 5.5 so definitely helped. +1
-
Jonathon almost 6 yearsAlthough at the time I asked this question (March 2015), Laravel 5.1 was not released (It was released in June 2015), I will accept this answer. For Laravel 5.0, see some of the other answers. Thanks, Julia!
-
CodeConnoisseur about 5 yearsIs there a way to change where the FormRequest redirects to or does it only redirect back when there are errors?
-
CodeConnoisseur about 5 yearsIs there a way to change where the FormRequest redirects to or does it only redirect back when there are errors?
-
Ratto over 3 yearsWhy is this method not documented until version 6.x? :-/