Laravel 5.1 Modify input before form request valid

2019-01-28 08:42发布

问题:

Is there a way to modify input fields inside a form request class before the validation takes place?

I want to modify some input date fields as follows but it doesn't seem to work.

When I set $this->start_dt input field to 2016-02-06 12:00:00 and $this->end_dt to 2016-02-06 13:00:00 I still get validation error "end_dt must be after start_dt". Which means the input request values aren't getting changed when you update $this->start_dt and $this->end_dt inside the rules() function.

public function rules()
{
    if ($this->start_dt){
        $this->start_dt = Carbon::createFromFormat('d M Y H:i:s', $this->start_dt . ' ' . $this->start_hr . ':'. $this->start_min . ':00');
    }

    if ($this->end_dt){
        $this->end_dt = Carbon::createFromFormat('d M Y H:i:s', $this->end_dt . ' ' . $this->end_hr . ':'. $this->end_min . ':00');
    }

    return [
        'start_dt' => 'required|date|after:yesterday',
        'end_dt' => 'required|date|after:start_dt|before:' . Carbon::parse($this->start_dt)->addDays(30)            
    ];
}

Note: start_dt and end_dt are date picker fields and the start_hr, start_min are drop down fields. Hence I need to create a datetime by combining all the fields so that I can compare.

回答1:

As of laravel 5.4 you can override the prepareForValidation method of the ValidatesWhenResolvedTrait to modify any input. Something similar should be possible for laravel 5.1.

Example in your Request

/**
 * Modify the input values
 *
 * @return void
 */
protected function prepareForValidation() {

    // get the input
    $input = array_map('trim', $this->all());

    // check newsletter
    if (!isset($input['newsletter'])) {
        $input['newsletter'] = false;
    }

    // replace old input with new input
    $this->replace($input);
}


回答2:

The FormRequest has a method validationData() that return the data to validate, so i'm overriding it in my form request:

<?php

namespace App\Http\Requests;

use Illuminate\Foundation\Http\FormRequest;

class MyClassRequest extends FormRequest
{
    ...
    /**
     * Get data to be validated from the request.
     *
     * @return array
     */
    public function validationData() {
        return array_merge(
            $this->all(),
            [
                'number' => preg_replace("/[^0-9]/", "", $this->number)
            ]
        );
    }
    ...
}

It work fine i'm using Laravel 5.4 :)



回答3:

You can do something like the following:

public function rules(Request $request)
{
    if ($request->has('start_dt')){
        $request->replace('start_dt', Carbon::createFromFormat('d M Y H:i:s', $request->start_dt . ' ' . $request->start_hr . ':'. $request->start_min . ':00'));
    }

    if ($request->has('end_dt')){
         $request->replace('end_dt' ,Carbon::createFromFormat('d M Y H:i:s', $request->end_dt . ' ' . $request->end_hr . ':'. $request->end_min . ':00'));
    }

    return [
        'start_dt' => 'required|date|after:yesterday',
        'end_dt' => 'required|date|after:start_dt|before:' . Carbon::parse($request->start_dt)->addDays(30)            
    ];
}


回答4:

Try these steps:

1- Middleware

first of all you should create a middleware in app/Http/Middleware:

<?php

namespace App\Http\Middleware;

use Illuminate\Foundation\Http\Middleware\TransformsRequest;

class DateTimeMiddleware extends TransformsRequest
{
    protected $fields = [
        'birth_date' => 'toGregorian',
        'created_at' => 'toDateTime',
    ];

    protected function transform($key, $value)
    {
        if (!array_key_exists($key, $this->fields)) {
            return $value;
        }

        $function = $this->fields[$key];

        return call_user_func($function, $value);
    }
}

with this middleware you can define fields that you want to manipulate them before calling validation and call a specific function to manipulate them.

note: i defined toGregorian and toDateTime in my own helper functions. you can handle it with your own functions

2- Kernel

then modify Http/Kernel.php like bellow:

protected $middlewareGroups = [
    'web' => [
        ...

        \App\Http\Middleware\EnglishStrings::class,
    ],

    'api' => [
        ...
    ],
];


回答5:

I took an alternative approach to this, as I want to be able to use $model->fill($validated); in my controllers. As such, I need to ensure that checkboxes have been included as false where they would otherwise be excluded from the array.

So, I created a trait, in app\Traits\ConvertBoolean.php, as follows:

<?php

namespace App\Traits;

trait ConvertBoolean
{
    // protected $boolean_attributes = [];

    /**
     * Override the FormRequest prepareForValidation() method to
     * add boolean attributes specified to the request data, setting
     * their value to the presence of the data in the original request.
     *
     * @return void
     */
    protected function prepareForValidation() {

        if (isset($this->boolean_attributes) && is_array($this->boolean_attributes)) {

            $attrs_to_add = [];

            foreach ($this->boolean_attributes as $attribute) {
                $attrs_to_add[$attribute] = $this->has($attribute);
            }

            $this->merge($attrs_to_add);
        }
    }
}

This trait looks for the existence of an array $this->boolean_attributes in the request. If it finds it, it goes through each one and adds the attribute to the request data with the value set to the presence of the attribute in the original request data.

It disregards the value of the HTML form's checkbox value. In most cases, this won't be a problem, but you could change the logic in the trait to look for specific values, if required.

Now that I have this trait, I can then use it in any request, like so:

use App\Traits\ConvertBoolean;

class MyUpdateRequest extends FormRequest
{
    use ConvertBoolean;

    protected $boolean_attributes = ['my_checkbox'];

    // ... other class code


    public function rules()
    {
        // Note that the data is added to the request data,
        // so you still need a validation rule for it, in
        // order to receive it in the validated data.
        return [
            'my_checkbox' => 'boolean',
            // other rules
        ];
    }
}

If you want to use $this->prepareForValidation() in your request, this is still possible.

Change MyRequest, as follows:

use App\Traits\ConvertBoolean;

class MyUpdateRequest extends FormRequest
{
    use ConvertBoolean {
        prepareForValidation as traitPrepareForValidation;
    }

    protected function prepareForValidation() {

        // the stuff you want to do in MyRequest
        // ...

        $this->traitPrepareForValidation();
    }

    // ...
}