laravel 入门(6)请求处理

在 Web 应用中,用户提交的数据往往是不可预测的,因此一个非常常见的需求是对用户提交的表单请求进行验证,以确保用户输入的是我们所期望的数据格式。

作为一个灵活的框架,Laravel 提供了多种方式对表单请求进行验证,你可以在控制器中通过 $this->validate() 方法验证用户请求,也可以通过单独的表单验证类定义验证规则,再将其注入到相应的控制器方法。

通过 validate 方法进行验证

在控制器中编写验证逻辑

通过 php artisan make:controller 生成的所有控制器默认都继承自基类 App\Http\Controllers\Controller,因此所有这些控制器都使用了 ValidatesRequests Trait,进而可以使用该 Trait 中提供的 validate() 方法对请求字段进行验证。

示例:

public function form(Request $request, $id)
{
    $this->validate($request, [
        'title' => 'bail|required|string|between:2,32',
        'url' => 'sometimes|url|max:200',
        'picture' => 'nullable|string'
    ]);

    return response('表单验证通过');
}

在该方法中,第一个参数是用户请求实例,第二个参数是以数组形式定义的请求字段验证规则,关于所有字段验证规则及其说明你可以在验证规则文档中查看,这里我们定义 title 字段是必填的,格式是字符串,且长度介于2~32之间,并且通过bail 指定任何一个验证规则不通过则立即退出,不再做后续校验;url 字段通过 sometimes 指定为存在时验证,如果填写了的话格式必须是 URL,且长度不能超过 200,每填写的话则不验证;最后图片路径允许为空。不同的验证规则之间通过 | 分隔。

如果表单验证通过,则继续向下执行,如果表单验证不通过,会抛出 ValidationException 异常,具体怎么处理这个异常要看请求方式,如果是 Ajax 请求的话,将会返回包含错误信息的 JSON 响应(错误码为 422),如果是正常的 POST 表单请求的话,会重定向到表单提交页,并包含所有用户输入和错误信息,以便重新渲染已填写表单并显示错误信息。

下面我们分别以 POST 提交表单和 Ajax 请求为例简单演示下验证错误信息的读取,首先来看 POST 提交表单。

在表单页面显示错误信息

在 Blade 模板中可以通过 $errors 获取验证错误信息,通过 old() 辅助函数可以获取用户上次输入数据:

<div id="app">
   <div class="container">
       @if ($errors->any())
           <div class="alert alert-danger">
               <ul>
                   @foreach ($errors->all() as $error)
                       <li>{{ $error }}</li>
                   @endforeach
               </ul>
           </div>
       @endif
       <form action="{{ route('form.submit') }}" method="POST">
           <div class="form-group">
               <label>标题</label>
               <input type="text" name="title" class="form-control" placeholder="输入标题" value="{{ old('title') }}">
           </div>
           <div class="form-group">
               <label>URL</label>
               <input type="text" name="url" class="form-control" placeholder="输入URL" value="{{ old('url') }}">
           </div>
           <fileupload-component></fileupload-component>
           <button type="submit" class="btn btn-primary">提交</button>
       </form>
   </div>
</div>

还可以在 $this->validate() 方法中的设置第三个参数来自定义错误消息:

$this->validate($request, [
   'title' => 'bail|required|string|between:2,32',
   'url' => 'sometimes|url|max:200',
   'picture' => 'nullable|string'
], [
   'title.required' => '标题字段不能为空',
   'title.string' => '标题字段仅支持字符串',
   'title.between' => '标题长度必须介于2-32之间',
   'url.url' => 'URL格式不正确,请输入有效的URL',
   'url.max' => 'URL长度不能超过200',
]);

Ajax 请求错误信息提示

接下来我们来看 Ajax 请求验证错误信息的获取和提示
首先在 RequestController 中修改 fileUpload 方法,设置上传文件字段的验证规则:

$this->validate($request, [
    'picture' => 'bail|required|image|mimes:jpg,png,jpeg|max:1024'
],[
    'picture.required' => '请选择要上传的图片',
    'picture.image' => '只支持上传图片',
    'picture.mimes' => '只支持上传jpg/png/jpeg格式图片',
    'picture.max' => '上传图片超过最大尺寸限制(1M)'
]);

然后到 FileUploadComponent.vue 中修改错误提示代码:

axios.post(
      '/form/file_upload',
      formData,
      {
          headers: {
              'Content-Type': 'multipart/form-data'
          }
      }
  ).then(function (response) {
      $('#picture-path').val(response.data.path);
      $('#picture-preview').html('<img src="' + response.data.path + '">')
  }).catch(function (error) {
      if (error.response.status === 422) {
          $.each(error.response.data.errors, function (field, errors) {
              $('#picture-preview').append('<div class="alert alert-danger">' + errors[0] + '</div>');
          });
      }
      console.log(error);
});

如果你使用的时 jQuery 的话,处理逻辑也是类似,根据错误码 422 进行处理。

通过 Validator::make 方法进行验证

如果你使用过 Laravel 自带脚手架代码实现登录认证的话,你可能会留意到 RegisterController 中对用户注册请求进行验证的时候,使用的是这样的验证代码:

protected function validator(array $data)
{
    return Validator::make($data, [
        'name' => 'required|string|max:255',
        'email' => 'required|string|email|max:255|unique:users',
        'password' => 'required|string|min:6|confirmed',
    ]);
}

这其实是通过 Validator 门面实现的验证,原理和上面通过 $this->validate() 一样,这是形式不同,这样做的一个好处是在非控制器类中也可以对字段进行验证,因为 validate 毕竟是 ValidatesRequests 中的方法,没有使用这个 Trait 的话就不能在代码中这么调用。如果我们将上面的表单请求改写为 Validator::make 来实现的话,代码是这样的:

Validator::make($request->all(), [
   'title' => 'bail|required|string|between:2,32',
   'url' => 'sometimes|url|max:200',
   'picture' => 'nullable|string'
], [
   'title.required' => '标题字段不能为空',
   'title.string' => '标题字段仅支持字符串',
   'title.between' => '标题长度必须介于2-32之间',
   'url.url' => 'URL格式不正确,请输入有效的URL',
   'url.max' => 'URL长度不能超过200',
])->validate();

除了第一个参数和最后要手动调动 validate() 方法外,其它参数都是一模一样的,底层的处理方式也是一样,所以其它地方的代码都不需要做任何更改。如果是在控制器中进行请求验证都可以,具体使用哪种方式,看你个人偏好了,如果是在其它地方比如服务类,可能 Validator::make 更合适些。

对于大量请求字段,或者复杂的请求验证,都写到控制器方法中显然会导致控制器的代码变得臃肿。

通过表单请求类实现请求字段验证和错误提示

果请求字段很多很复杂,都写到控制器方法里面会导致控制器臃肿,从单一职责原则来说需要将表单请求验证拆分出去,然后通过类型提示的方式注入到控制器方法。

定义表单请求类

首先,我们需要需要创建一个表单请求类,这可以通过 Artisan 命令来完成:

php artisan make:request SubmitFormRequest

该命令会在 app/Http/Requests 目录下新增一个 SubmitFormRequest.php 文件,并且初始化代码如下:

namespace App\Http\Requests;

use Illuminate\Foundation\Http\FormRequest;

class SubmitFormRequest extends FormRequest
{
    /**
     * Determine if the user is authorized to make this request.
     *
     * @return bool
     */
    public function authorize()
    {
        return false;
    }

    /**
     * Get the validation rules that apply to the request.
     *
     * @return array
     */
    public function rules()
    {
        return [
            //
        ];
    }
}

authorize() 方法用于检查用户权限,如果返回 false 则表示用户无权提交表单,会抛出权限异常中止请求,现在我们将其调整为返回 true 即可,然后我们在 rules() 方法中定义请求字段验证规则

public function rules()
{
    return [
        'title' => 'bail|required|string|between:2,32',
        'url' => 'sometimes|url|max:200',
        'picture' => 'nullable|string'
    ];
}

然后你可能要问那自定义错误提示消息在哪里定义呢?既然是在类中,自然可以通过方法来实现,我们只需重写父类的 messages() 方法即可:

public function messages()
{
    return [
        'title.required' => '标题字段不能为空',
        'title.string' => '标题字段仅支持字符串',
        'title.between' => '标题长度必须介于2-32之间',
        'url.url' => 'URL格式不正确,请输入有效的URL',
        'url.max' => 'URL长度不能超过200',
    ];
}

这样,我们就将控制器方法中的表单请求字段验证逻辑全部迁移过来了。

表单请求类的执行

接下来,问题又来了,这段表单请求字段验证逻辑放在哪里执行呢?答案是将其以类型提示的方式注入到请求路由对应的控制器方法即可,在本例中,就是 RequestController 的 form 方法:

public function form(SubmitFormRequest $request)
{
    return response('表单验证通过');
}

Laravel 底层在解析这个控制器方法的参数时,如果发现这个请求是一个表单请求类,则会自动执行其中定义的字段验证规则对请求字段进行验证,如果验证成功则继续执行控制器中的方法,否则会抛出验证失败异常。由于该表单请求类也是 Illuminate\Http\Request 的子类,所以后续获取请求字段值也可以通过 $request 来获取,将表单请求验证和请求实例参数合二为一,非常方便。

数组请求字段验证

某些场合下,我们的表单请求中可能会包含数组字段,比如 books[] 或者 books[author],甚至可能是更加复杂的 books[test][author],对于这种数组字段的验证,在 Laravel 中也不在话下:

'books' => 'required|array',   # 验证 books[]
'books.author' => 'required|max:10',  # 验证 books[author]
'books.*.author' => 'required|max:10',  # 验证 books[test][author]

更多请求验证字段规则,请查看官方文档

通过匿名函数实现自定义规则

我们先演示下如何在控制器方法中调用 $this->validate() 时自定义验证规则,以 title 字段为例,除了系统提供的字段验证规则之外,有时候我们还会禁止用户输入包含敏感词的字段,在我们国家,这也是司空见惯的事情,那要如何实现这个 Laravel 办不到的事情呢,通过自定义验证规则:

$this->validate($request, [
   'title' => [
       'bail',
       'required',
       'string',
       'between:2,32',
       function($attribute, $value, $fail) {
           if (strpos($value, '敏感词') !== false) {
               return $fail('标题包含了系统禁用的敏感词');
           }
       },
   ],
   'url' => 'sometimes|url|max:200',
   'picture' => 'nullable|string'
], [
   'title.required' => '标题字段不能为空',
   'title.string' => '标题字段仅支持字符串',
   'title.between' => '标题长度必须介于2-32之间',
   'url.url' => 'URL格式不正确,请输入有效的URL',
   'url.max' => 'URL长度不能超过200',
]);

要为某个字段自定义验证规则,原来通过 | 分隔多个规则的组合规则字符串已经实现不了了,需要将其改成数组的方式,然后将自定义规则以匿名函数的方式添加到数组最后,如上面的代码所示,该匿名函数第一个参数是字段名,第二个参数是字段值,第三个参数是校验失败用于返回的函数名。如果检查到输入标题包含敏感词,则认为验证不通过,返回错误信息(我这里的主要目的是演示如何自定义验证规则,实际环境中不要这样校验敏感词哈,效率太低)。

如果你使用的是 Validator::make 进行请求字段验证的话,实现方式完全一样,不再赘述,即使是在表单请求类 SubmitFormRequest 中,也是一样的,把代码迁移过去就好了。

通过创建规则类自定义验证规则

除了通过匿名函数之外,还可以通过创建一个规则类来实现验证规则的自定义:

php artisan make:rule SensitiveWordRule

该命令会在 app 目录下创建一个 Rules 子目录,并在这个子目录下新增 SensitiveWordRule.php 文件,我们可以将验证通过条件定义到该类的 passes 方法中:

public function passes($attribute, $value)
{
    return strpos($value, '敏感词') === false;
}

如果输入值中包含敏感词,则认为验证失败,然后在 message 方法中修改验证失败的错误消息,由于我们这个规则类是通用的,所以将字段名通过 :attribute 动态注入:

public function message()
{
    return ':attribute输入字段中包含敏感词';
}

最后,将自定义验证规则的匿名函数修改为实例化自定义规则类即可:

public function rules()
{
    return [
        'title' => [
            'bail',
            'required',
            'string',
            'between:2,32',
            new SensitiveWordRule()
        ],
        'url' => 'sometimes|url|max:200',
        'picture' => 'nullable|string',
    ];
}

此外,再抛一个知识点,我们可以在表单请求类中通过重写父类 attributes() 方法自定义字段名:

public function attributes()
{
    return [
        'title' => '标题',
        'url' => 'URL',
        'picture' => '图片'
    ];
}

这样,在验证规则类 SensitiveWordRule 验证失败时返回错误提示时,就可以将 :attribute 替换为 标题,而不是默认的 title 了。

很显然,匿名函数虽然方便,但是解决不了代码复用的问题,通过自定义验证规则类则可以很好的解决,一次定义,多处复用。

posted @ 2020-09-02 02:41  caibaotimes  阅读(408)  评论(0)    收藏  举报