Laravel 5表单请求数据预处理


Laravel 5 Form Request data pre-manipulation

我正在处理一个表单,用户可以在其中更新自己的出生日期。该表单为用户提供了daymonthyear三个单独的字段。当然,在服务器端,我想将这3个单独的字段视为一个值,即yyyy-mm-dd

因此,在验证和更新我的数据库之前,我想更改表单请求,通过将yearmonthday-字符连接来创建date_of_birth字段,以创建我需要的日期格式(并且可能取消设置原始的3个字段)。

用我的控制器手动实现这一点不是问题。我可以简单地获取输入,将用-字符分隔的字段连接在一起,然后取消设置。然后,我可以手动验证,然后传递给一个命令来处理处理。

然而,我更喜欢使用FormRequest来处理验证,并将其注入到我的控制器方法中。因此,我需要一种在执行验证之前实际修改表单请求的方法。

我确实发现了以下类似的问题:Laravel 5请求更改数据

它建议重写表单请求上的all方法,以包含在验证之前操作数据的逻辑。

<?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;
    }

这对验证来说是很好的,但重写all方法实际上并不会修改表单请求对象上的数据。因此,在执行命令时,表单请求包含原始的未修改数据。除非我使用现在被覆盖的all方法来提取数据。

我正在寻找一种更具体的方法来修改表单请求中的数据,而不需要调用特定的方法。

干杯

在laravel 5.1中,您可以执行

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

由于Laravel 5.4,您可以在FormRequest类上使用prepareForValidation方法。

<?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,
        ]);
    }
}

这里有更详细的内容:https://sampo.co.uk/blog/manipulating-request-data-before-performing-validation-in-laravel

注意:这个问题和答案都是在Laravel 5.1发布之前发布的,都是目标5.0。对于5.1及以上版本,请参阅@Julia Shestakova的回答或@BenSampo的回答,以获得更现代的解决方案。


经过一番折腾,我想出了以下办法:

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;
    }
}

然后在扩展类时,您可以覆盖modifyInput方法,如下所示:

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;
    }
}

这似乎能满足我的需要。我不确定这样做有什么缺点,所以我欢迎任何评论/批评。

上面由The Shift Exchange给出的答案也会很好。

我采用了与Julia Logvina类似的方法,但我认为这种方法在验证(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();
    }
}

这将扩展默认的getValidatorInstance,这样我们就可以在请求到达验证器之前修改请求中的输入值(防止它使用原始的未修改数据)。一旦数据被修改,它就会触发原始的getValidatorInstance,然后一切照常进行。

您可以在请求中使用$this->replace(array())$this->merge(array())您的新字段。我在上面的片段中包含了一个如何同时做到这两个的示例。

replace()将用您提供的数组替换所有字段。

merge()将在您的请求中添加一个新字段。

我认为这是最好的方法:Laravel 5.1在表单请求验证之前修改输入

在Laravel 5.4+中有一个专门的方法来处理这样的东西,所以请使用它:prepareForValidation

您仍然可以覆盖all()方法,但可以像这个一样尝试

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

然后,您实际上不会自己调用方法——在执行规则时,它将由验证器本身调用。

我也需要一种快速而肮脏的方法来实现这一点。我想使用Shift Exchanges解决方案,但由于调用了创建无限递归循环的$this,所以它没有起作用。快速更改为引用父方法将解决问题:

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

HTH其他需要帮助的人。