Laravel 5 表单请求数据预操作

Posted

技术标签:

【中文标题】Laravel 5 表单请求数据预操作【英文标题】:Laravel 5 Form Request data pre-manipulation 【发布时间】:2015-05-05 10:46:31 【问题描述】:

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

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

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

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

我确实发现了以下类似的问题:Laravel 5 Request - altering data

它建议覆盖表单请求上的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 方法来提取数据。

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

干杯

【问题讨论】:

【参考方案1】:

您仍然会覆盖 all() 方法 - 但可以这样尝试

public function all()

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

那么你实际上并没有自己调用该方法——它会在执行规则时由验证器本身调用。

【讨论】:

感谢您的回复。没有更清洁的“一次性”方式吗?感觉很脏,每次请求数据时都必须对数据进行操作。从代码来看,似乎每次请求数据时都会调用all() 函数(即来自inputexcept 等)。 这是一种非常干净的方式——因为它在 FormRequest 对象中被巧妙地处理。这意味着控制器和应用程序的其他方面不知道发生了什么。每次调用all() 时都会发生这种情况,这不是问题——您所说的优化级别是微不足道的。专注于编写好的干净可维护的代码会有更好的结果:) 是的,在这种情况下这绝对是微不足道的。但从理论上讲,如果这里有很多事情要做,那可能就是另一回事了。我更喜欢尽可能编写最佳代码。我将这种方法比作获取我们在每次迭代中循环的数组的长度。它有效,优化可能是微不足道的,但它都安装了。我尝试寻找其他方法来覆盖并得出结论,在Symfony\Component\HttpFoundation\Request 中找到的initialize 方法可能是要走的路。对此有什么想法吗?它是从构造函数中调用的。 问题是 - all() 方法已经每次迭代无论如何上进行数组循环。如果您查看原始的 all() 方法 - 就是这样:return array_replace_recursive($this-&gt;input(), $this-&gt;files-&gt;all())。因此,您进行此修改不会对您的性能产​​生任何影响。您正在尝试优化不需要优化的流程...【参考方案2】:

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


经过一番折腾,我想出了以下几点:

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;
    


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

上面 The Shift Exchange 给出的答案也可以正常工作。

【讨论】:

+1 这实际上是一种非常好的方式,我喜欢它。虽然我会通过 $source = $this-&gt;getInputSource(); 缓存 sourceInput【参考方案3】:

我也需要一种快速而肮脏的方法来实现这一点。我想使用 The 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();

还有其他需要帮助的人。

【讨论】:

【参考方案4】:

在 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();

【讨论】:

虽然在我问这个问题的时候(2015 年 3 月),Laravel 5.1 还没有发布(它是在 2015 年 6 月发布的),但我会接受这个答案。对于 Laravel 5.0,请参阅其他一些答案。谢谢,朱莉娅!【参考方案5】:

我对 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-&gt;replace(array())$this-&gt;merge(array()) 新字段。我在上面的 sn-p 中包含了一个如何同时执行这两个操作的示例。

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

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

【讨论】:

有没有办法改变 FormRequest 重定向到的位置,还是只在出现错误时才重定向回来?【参考方案6】:

我认为这是最好的方法:Laravel 5.1 Modify input before form request validation

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

【讨论】:

当然,在 5.4 中,这无疑是该问题的最佳解决方案。但是,对于 5.4 之前的版本(这个问题是在 2015 年 3 月提出的,当时的版本是 5.0),那么下面给出的其他答案更合适,例如 @Julia Logvina 为 5.1 给出的答案,这是一个长期支持 (LTS) 版本 Jep,但是像我这样的人如果他们在 5.x 上寻找一般主题仍然会发现这个,所以我认为提及 5.4+ 的解决方案是有效的;) 感谢@Thomas Venturini!使用 5.5 肯定有帮助。 +1【参考方案7】:

从 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

【讨论】:

有没有办法改变 FormRequest 重定向到的位置,还是只在出现错误时才重定向回来? 为什么直到版本 6.x 才记录此方法? :-/

以上是关于Laravel 5 表单请求数据预操作的主要内容,如果未能解决你的问题,请参考以下文章

Laravel 5 更改表单请求失败的验证行为

在 laravel 5 中的表单请求验证后传递旧输入

方法验证不存在 - Laravel 5.4

基于 Web 请求预填充 Django 表单

Laravel 5.5 - 同时验证多个表单请求

使用 laravel 验证检查唯一的日期/时间