Laravel中使用FormRequest进行表单验证方法及问题汇总


Posted in PHP onJune 19, 2016

在`Laravel`中,每一个请求都会被封装为一个`Request`对象,`Form Request`对象就是包含了额外验证逻辑(以及访问权限控制)的自定义`Request`类。 本文分析了FormRequest异常的处理流程并提出了自定义处理FormRequest验证失败的思路。

所有示例基于Laravel 5.1.39 (LTS)

今天天气不错,我们来说说表单验证。

Controller中做表单验证

有的同学把表单验证逻辑写在Controller中,例如这个对用户提交评论内容的验证:

<?php

// ... 

use Validator;

class CommentController
{

  public function postStoreComment(Request $request)
  {
    $validator = Validator::make($request->all(), [
      'comment' => 'required', // 只是实例,就写个简单的规则,你的网站要是这么写欢迎在评论里贴网址
    ]);

    if ($validator->fails()) {
      return redirect()
        ->back()
        ->withErrors($validator)
        ->withInput();
    }
  }

这样写的话,表单验证和业务逻辑挤在一起,我们的Controller中就会有太多的代码,而且重复的验证规则基本也是复制粘贴。

我们可以利用Form Request来封装表单验证代码,从而精简Controller中的代码逻辑,使其专注于业务。而独立出去的表单验证逻辑甚至可以复用到其它请求中,例如修改评论。

什么是Form Request

在Laravel中,每一个请求都会被封装为一个Request对象,Form Request对象就是包含了额外验证逻辑(以及访问权限控制)的自定义Request类。

如何使用Form Request做表单验证

Laravel提供了生成Form Request的Artisan命令:

<code>$ php artisan make:request StoreCommentRequest</code>

于是就生成了app/Http/Requests/StoreCommentRequest.php,让我们来分析一下内容:

<?php

namespace App\Http\Requests;

use App\Http\Requests\Request; // 可以看到,这个基类是在我们的项目中的,这意味着我们可以修改它

class StoreCommentRequest extends Request
{
  /**
   * Determine if the user is authorized to make this request.
   *
   * @return bool
   */
  public function authorize() // 这个方法可以用来控制访问权限,例如禁止未付费用户评论…
  {
    return false; // 注意!这里默认是false,记得改成true
  }

  /**
   * Get the validation rules that apply to the request.
   *
   * @return array
   */
  public function rules() // 这个方法返回验证规则数组,也就是Validator的验证规则
  {
    return [
      //
    ];
  }
}

那么很容易,我们除了让authorize方法返回true之外,还得让rules方法返回我们的验证规则:

<?php

// ...

  public function rules()
  {
    return [

    ];
  }

// ...

接着修改我们的Controller:

<?php

// ...

  // 之前:public function postStoreComment(Request $request)
  public function postStoreComment(\App\Http\Requests\StoreCommentRequest $request)
  {
    // ...
  }

// ...

这样Laravel便会自动调用StoreCommentRequest进行表单验证了。

异常处理

如果表单验证失败,Laravel会重定向到之前的页面,并且将错误写到Session中,如果是AJAX请求,则会返回一段HTTP状态为422的JSON数据,类似这样:

<code>{comment: ["The comment field is required."]}</code>

这里就不细说提示信息怎么修改了,如果有人想看相关教程,可以留言。

我们主要来说说怎么定制错误处理。

通常来说,Laravel中的错误都是异常(Exception),我们都可以在app\Exceptions\handler.php中进行统一处理。Form Request确实也抛出了一个Illuminate\Http\Exception\HttpResponseException异常,但这个异常是在路由逻辑中就被特殊处理了。

首先我们来看看Form Request是如何被执行的:

Illuminate\Validation\ValidationServiceProvider:

<?php

namespace Illuminate\Validation;

use Illuminate\Support\ServiceProvider;
use Illuminate\Contracts\Validation\ValidatesWhenResolved;

class ValidationServiceProvider extends ServiceProvider
{
  /**
   * Register the service provider.
   *
   * @return void
   */
  public function register()
  {
    $this->registerValidationResolverHook(); // 看我看我看我

    $this->registerPresenceVerifier();

    $this->registerValidationFactory();
  }

  /**
   * Register the "ValidatesWhenResolved" container hook.
   *
   * @return void
   */
  protected function registerValidationResolverHook() // 对,就是我
  {
    // 这里可以看到对`ValidatesWhenResolved`的实现做了一个监听
    $this->app->afterResolving(function (ValidatesWhenResolved $resolved) {
      $resolved->validate(); // 然后调用了它的`validate`方法进行验证
    });
  }

// ...

你猜对了,Form Request就实现了这个Illuminate\Contracts\Validation\ValidatesWhenResolved接口:

<?php 

namespace Illuminate\Foundation\Http;

use Illuminate\Http\Request;
use Illuminate\Http\Response;
use Illuminate\Http\JsonResponse;
use Illuminate\Routing\Redirector;
use Illuminate\Container\Container;
use Illuminate\Contracts\Validation\Validator;
use Illuminate\Http\Exception\HttpResponseException;
use Illuminate\Validation\ValidatesWhenResolvedTrait;
use Illuminate\Contracts\Validation\ValidatesWhenResolved; // 是你
use Illuminate\Contracts\Validation\Factory as ValidationFactory;

// 我们`app\Http\Requests\Request`便是继承于这个`FormRequest`类
class FormRequest extends Request implements ValidatesWhenResolved // 就是你
{
  use ValidatesWhenResolvedTrait; // 这个我们待会儿也要看看

  // ...

FormRequest基类中的validate方法是由这个Illuminate\Validation\ValidatesWhenResolvedTrait实现的:

Illuminate\Validation\ValidatesWhenResolvedTrait:

<?php

namespace Illuminate\Validation;

use Illuminate\Contracts\Validation\ValidationException;
use Illuminate\Contracts\Validation\UnauthorizedException;

/**
 * Provides default implementation of ValidatesWhenResolved contract.
 */
trait ValidatesWhenResolvedTrait
{
  /**
   * Validate the class instance.
   *
   * @return void
   */
  public function validate() // 这里实现了`validate`方法
  {
    $instance = $this->getValidatorInstance(); // 这里获取了`Validator`实例

    if (! $this->passesAuthorization()) {
      $this->failedAuthorization(); // 这是调用了访问授权的失败处理
    } elseif (! $instance->passes()) {
      $this->failedValidation($instance); // 这里调用了验证失败的处理,我们主要看这里
    }
  }

  // ...

在validate里,如果验证失败了就会调用$this->failedValidation(),继续:

Illuminate\Foundation\Http\FormRequest:

<?php

// ...

  /**
   * Handle a failed validation attempt.
   *
   * @param \Illuminate\Contracts\Validation\Validator $validator
   * @return mixed
   */
  protected function failedValidation(Validator $validator)
  {
    throw new HttpResponseException($this->response( // 这里抛出了传说中的异常
      $this->formatErrors($validator)
    ));
  }

终于看到异常了!可是这个异常在另一个地方被处理了:

Illuminate\Routing\Route:

<?php

  // ...

  /**
   * Run the route action and return the response.
   *
   * @param \Illuminate\Http\Request $request
   * @return mixed
   */
  public function run(Request $request)
  {
    $this->container = $this->container ?: new Container;

    try {
      if (! is_string($this->action['uses'])) {
        return $this->runCallable($request);
      }

      if ($this->customDispatcherIsBound()) {
        return $this->runWithCustomDispatcher($request);
      }

      return $this->runController($request);
    } catch (HttpResponseException $e) { // 就是这里
      return $e->getResponse(); // 这里直接返回了Response给客户端
    }
  }

  // ...

至此,整个思路已然清晰,不过我们还是看看这里生成的HttpResponseException异常中的Response是怎么生成的:

Illuminate\Foundation\Http\FormRequest:

<?php

// ...

  // 132行:
  if ($this->ajax() || $this->wantsJson()) { // 对AJAX请求的处理
    return new JsonResponse($errors, 422);
  }

  return $this->redirector->to($this->getRedirectUrl()) // 对普通表单提交的处理
                  ->withInput($this->except($this->dontFlash))
                  ->withErrors($errors, $this->errorBag);

// ...

相信你都看明白了。

如何实现自定义错误处理,这里提供两个思路,都需要重写app\Http\Requests\Request的failedValidation:

抛出一个新异常,继承HttpResponseException异常,重新实现getResponse方法,这个异常类我们可以放到app/Exceptions/下便于管理,错误返回依然交给Laravel;

抛出一个我们自定义的异常,在app\Exceptions\handler中处理。

具体实现这里就不写啦(参阅Laravel文档中关于错误处理部分,中文文档传送门),如果你有别的方法或者想法可以在评论中和我交流。

补充

如果你的Controller使用Illuminate\Foundation\Validation\ValidatesRequests这个Trait的validate方法进行验证,同样的,这里验证失败也会抛出Illuminate\Http\Exception\HttpResponseException异常,可以参考上面的解决方案进行处理。

PHP 相关文章推荐
一个很方便的 XML 类!!原创的噢
Oct 09 PHP
ZF等常用php框架中存在的问题
Jan 10 PHP
PHP统计目录下的文件总数及代码行数(去除注释及空行)
Jan 17 PHP
php中mysql连接和基本操作代码(快速测试使用,简单方便)
Apr 25 PHP
PHP函数eval()介绍和使用示例
Aug 20 PHP
php从memcache读取数据再批量写入mysql的方法
Dec 29 PHP
php实现统计网站在线人数的方法
May 12 PHP
PHP二维数组排序简单实现方法
Feb 14 PHP
php的socket编程详解
Nov 20 PHP
THINKPHP截取中文字符串函数实例代码
Mar 20 PHP
基于ThinkPHP5框架使用QueryList爬取并存入mysql数据库操作示例
May 25 PHP
php如何获取Http请求
Apr 30 PHP
php打乱数组二维数组多维数组的简单实例
Jun 17 #PHP
PHP 将数组打乱 shuffle函数的用法及简单实例
Jun 17 #PHP
PHP 数组基本操作方法详解
Jun 17 #PHP
全面了解PHP中的全局变量
Jun 17 #PHP
浅析PHP7新功能及语法变化总结
Jun 17 #PHP
PHP设计模式之迭代器模式
Jun 17 #PHP
浅析PHP中的i++与++i的区别及效率
Jun 15 #PHP
You might like
PHP 第三节 变量介绍
2012/04/28 PHP
46 个非常有用的 PHP 代码片段
2016/02/16 PHP
Yii框架表单模型和验证用法
2016/05/20 PHP
Yii中的cookie的发送和读取
2016/07/27 PHP
利用PHP如何写APP接口详解
2016/08/23 PHP
PHP封装的PDO数据库操作类实例
2017/06/21 PHP
用jquery与css打造个性化的单选框和复选框
2010/10/20 Javascript
JS获取并操作iframe中元素的方法
2013/03/21 Javascript
JavaScript面向对象的实现方法小结
2015/04/14 Javascript
jQuery+css实现的换页标签栏效果
2016/01/27 Javascript
Node.js读写文件之批量替换图片的实现方法
2016/09/07 Javascript
JS 循环li添加点击事件 (闭包的应用)
2016/12/10 Javascript
easyui datebox 时间限制,datebox开始时间限制结束时间,datebox截止日期比起始日期大的实现代码
2017/01/12 Javascript
基于JavaScript实现验证码功能
2017/04/01 Javascript
利用jQuery实现简单的拖曳效果实例代码
2017/10/20 jQuery
Vue实现todolist删除功能
2018/06/26 Javascript
小程序server请求微信服务器超时的解决方法
2019/05/21 Javascript
使用webpack搭建vue项目及注意事项
2019/06/10 Javascript
Vue动态加载图片在跨域时无法显示的问题及解决方法
2020/03/10 Javascript
[01:48]2018DOTA2亚洲邀请赛主赛事第二日五佳镜头 VG完美团战逆转TNC
2018/04/05 DOTA
Python常用正则表达式符号浅析
2014/08/13 Python
浅谈Python中的数据类型
2015/05/05 Python
python自动发邮件总结及实例说明【推荐】
2019/05/31 Python
django的auth认证,authenticate和装饰器功能详解
2019/07/25 Python
解决安装python3.7.4报错Can''t connect to HTTPS URL because the SSL module is not available
2019/07/31 Python
2020新版本pycharm+anaconda+opencv+pyqt环境配置学习笔记,亲测可用
2020/03/24 Python
OpenCV 使用imread()函数读取图片的六种正确姿势
2020/07/09 Python
澳大利亚最好的在线时尚精品店:Princess Polly
2018/01/03 全球购物
澳大利亚最早和最古老的巨型游戏专家:Yardgames
2020/02/20 全球购物
生物科学专业职业规划书范文
2014/02/11 职场文书
爱我中华演讲稿
2014/05/20 职场文书
个人批评与自我批评材料
2014/10/17 职场文书
白鹤梁导游词
2015/02/06 职场文书
大学入学感言
2015/08/01 职场文书
frg-100简单操作(设置)说明
2022/04/05 无线电
vue3语法糖内的defineProps及defineEmits
2022/04/14 Vue.js