Laravel 5 Form Request data pre-manipulation

2019-02-04 06:29发布

问题:

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 and year. 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 concatenating year, month and day 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 overridden all 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

回答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();
}


回答2:

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.



回答3:

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.



回答4:

You still override the all() method - but try it like this

public function all()
{
    $input = $this->all();
    $input['date_of_birth'] = $input['year'].'-'.$input['month'].'-'.$input['day'];
    $this->replace($input);
    return $this->all();
}

Then you dont actually call the method yourself - it will be called by the validator itself when doing the rules.



回答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



回答6:

I too needed a quick and dirty way to accomplish this. I wanted to use The Shift Exchanges solution but it didn't work because of the calls to $this which creates an infinite recursive loop. A quick change to reference the parent method instead will fix the issue:

public function all()
{
    $input = parent::all();
    $input['date_of_birth'] = $input['year'].'-'.$input['month'].'-'.$input['day'];
    $this->replace($input);
    return parent::all();
}

HTH others in need.



回答7:

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