Laravel 之 -- 验证 (上)

February . 05 . 2019

简介

Laravel 提供了几种方法来验证应用程序的输入数据(incoming data)。默认,Laravel 的基类控制器使用了 validatesRequests trait。这个 trait 里提供了一种方便验证传入的 HTTP 请求、带有丰富验证规则的方法。

快速上手

要了解 Laravel 强大的验证功能,我们需要看一个完整实例——验证表单,并且显示错误消息给用户。

定义路由

首先,在 routes/web.php 中,包含如下两个路由:

Route::get('posts/create', 'PostsController@create');

Route::post('posts', 'PostsController@store');

GET 路由会给用户显示一个创建博客文章的表单,而 POST 路由用来保存新的博客文章到数据库。

创建控制器

接下里,我们看下处理这些路由的控制器 PostsController,不过现在 store 方法还是空的:

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use App\Http\Controllers\Controller;

class PostsController extends Controller
{
    /**
     * Show the form to create a new blog post.
     *
     * @return Response
     */
    public function create()
    {
        return view('posts.create');
    }

    /**
     * Store a new blog post.
     *
     * @param  Request  $request
     * @return Response
     */
    public function store(Request $request)
    {
        // Validate and store the blog post...
    }
}

书写验证逻辑

现在,我们在 store 方法里验证提交过来的新博客文章的字段 titlebody。我们使用 Illuminate\Http\Request 对象上的 validate 方法。如果字段经过验证规则检验通过,则正常执行后面的代码;如果没通过,一个携带正确错误响应的异常会自动发送给用户。对于传统 HTTP 请求,会生成一个重定向响应;对于 AJAX 请求,会生成一个 JSON 响应。

为了更好理解 validate 方法,我们看下 store 方法内容:

/**
 * Store a new blog post.
 *
 * @param  Request  $request
 * @return Response
 */
public function store(Request $request)
{
    $request->validate([
        'title' => 'required|unique:posts|max:255',
        'body' => 'required',
    ]);

    // The blog post is valid, store in database...
}

我们给 validate 方法传递了期望的验证规则。再一次,如果验证失败,一个 一个携带正确错误信息的响应会自动生成;如果验证成功,我们会继续正常执行后面的代码。

在第一次验证失败时停止

有时,你希望在第一个验证规则失败时,就停止该字段接下来的规则验证。为此,请使用 bail 规则:

$request->validate([
    'title' => 'bail|required|unique:posts|max:255',
    'body' => 'required',
]);

在上面的例子里,如果 title 属性的 unique 规则验证失败了,就不再检查 max 规则。这些规则是按照顺序从左到右一个一个验证的。

嵌套属性的验证方式

如果 HTTP 请求中包含「嵌套」参数,你可以使用「点」(.)语法引用它们:

$request->validate([
    'title' => 'required|unique:posts|max:255',
    'author.name' => 'required',
    'author.description' => 'required',
]);

显示验证错误

如果输入数据字段没有通过验证规则呢?之前提到过,Laravel 会自动重定向到之前的页面,并携带正确的错误信息。另外,这些错误信息是自动 [闪存到会话][1] 中的。

请注意,我们不必在 GET 路由中将错误消息显式绑定到视图。这是因为 Laravel 会自动检查会话数据里的错误消息,并自动将这些错误数据绑定到视图的一个变量 $errors 里。$errors 变量是 Illuminate\Support\MessageBag 实例。

提示! $errors 这个变量是通过 Illuminate\View\Middleware\ShareErrorsFromSession 中间件绑定给视图的。这个中间件包含在了 web 中间件组里,因此 所有视图文件里总是会有这个 $errors 变量,你无需担心它没有,放心使用吧!

在我们的例子里,当 store 方法里的验证规则失败时,我们就会跳转到创建博客文章的页面,在这里我们可以显示错误消息:

<!-- /resources/views/posts/create.blade.php -->

<h1>创建博客文章</h1>

@if ($errors->any())
    <div class="alert alert-danger">
        <ul>
            [@foreach](https://learnku.com/users/5651) ($errors->all() as $error)
                <li>{{ $error }}</li>
            @endforeach
        </ul>           
    </div>
@endif

<!-- Create Post Form -->

验证可选字段

默认,Laravel 项目 App\Http\Kernel 类中定义的全局中间件组里包含 TrimStringConvertEmptyStringsToNull 这两个。而且输入数据字段的 null 值会被验证器看做无效的,如果允许字段为取 null 值,那么加上 nullable 这个验证规则就可以了。

$request->validate([
    'title' => 'required|unique:posts|max:255',
    'body' => 'required',
    'published_at' => 'nullable|date',
]);

在这个例子里,我们指定了 published_at 字段可以取 null 值或者是一个有效的日期表示。如果没有加 nullable 这个验证规则,验证器会把 null 看做无效日期。

AJAX 请求 & 验证

对 AJAX 请求使用 validate 方法,Laravel 不会自动生成一个重定向响应,相反,Laravel 会生成一个包含验证错误消息的 JSON 响应。该 JSON 响应将使用 422 HTTP 状态代码发送。

表单请求验证

创建表单请求

对于更加复杂的验证场景,可以使用「表单请求」。何为表单请求?

包含字段验证逻辑的自定义请求类。

实际上,这个表单请求类,并不是只能请求提交的表单字段,还可以处理 AJAX 请求提交过来的字段数据。所以更准确的叫法应该是「包含处理字段验证功能的请求类」,但太长了,是不?

创建一个表单请求类,使用 Artisan 命令 make:request

php artisan make:request StoreBlogPost

生成的这个 StoreBlogPost 类,会保存在 app/Http/Requests 目录下。如果目录没有不存在,会在第一次运行 make:request 命令时创建。接下来,我们在 rules 方法里添加一些验证规则:

/**
 * Get the validation rules that apply to the request.
 *
 * @return array
 */
public function rules()
{
    return [
        'title' => 'required|unique:posts|max:255',
        'body' => 'required',
    ];
}

So,这个表单请求类怎么生效呢?你可以在控制器方法里将它作为依赖注入。发起的表单请求会在控制器方法调用前,经表单请求类验证,就是说,你无需在控制器中混杂验证逻辑了:

/**
 * Store the incoming blog post.
 *
 * @param  StoreBlogPost  $request
 * @return Response
 */
public function store(StoreBlogPost $request)
{
    // The incoming request is valid...
}

如果验证失败,会自动生成一个重定向响应到之前的地址。错误数据会被闪存进会话,以便在前端显示。如果时 AJAX 请求,Laravel 会返回一个包含验证错误消息的 JSON 响应。该 JSON 响应使用 422 HTTP 状态代码发送。

表单请求的 after 钩子方法……

你可以为表单请求添加一个 after 钩子方法,这是在 withValidator 中定义的。 此方法接收基于当前请求构造的验证器实例对象,让你在实际验证字段之前,调用验证器实例对象的任何方法:

/**
 * Configure the validator instance.
 *
 * @param  \Illuminate\Validation\Validator  $validator
 * @return void
 */
public function withValidator($validator)
{
    $validator->after(function ($validator) {
        if ($this->somethingElseIsInvalid()) {
            $validator->errors()->add('field', 'Something is wrong with this field!');
        }
    });
}

授权表单请求

表单请求类也可以包含一个 authorize 方法,在此方法内,你可以检查认证用户是否有更新指定资源的权限。例如,你可以判断一个用户是否可以更新指定的博客文章的评论:

/**
 * Determine if the user is authorized to make this request.
 *
 * @return bool
 */
public function authorize()
{
    $comment = Comment::find($this->route('comment'));

    return $comment && $this->user()->can('update', $comment);
}

由于所有的表达请求类继承了 Laravel 基类请求类,我们可以使用 user 方法获得当前认证用户。注意上面调用 route 方法的地方,该方法用来获得路由参数 {comment}

Route::post('comments/{comment}');

如果 authorize 方法返回 false,那么一个携带 403 状态码的 HTTP 响应会自动返回,控制器方法不会执行。

如果你要把认证逻辑放在别处,只要让 authorize 方法直接返回 true 就可以了:

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

自定义错误消息

你可以在表单请求类里使用 messages 方法自定义默认显示的错误消息内容。这个方法返回一个带有键值对的数组,键是「属性.规则」的形式,值是对应规则验证失败时的错误消息内容:

/**
 * Get the error messages for the defined validation rules.
 *
 * @return array
 */
public function messages()
{
    return [
        'title.required' => 'A title is required',
        'body.required'  => 'A message is required',
    ];
}

手动创建验证器

如果你不用请求实例上的 validate 方法,那么可以使用 Validator 门面手动创建一个验证器。调用门面上的 make 方法,就会生成一个新的验证器实例:

<?php

namespace App\Http\Controllers;

use Validator;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;

class PostsController extends Controller
{
    /**
     * Store a new blog post.
     *
     * @param  Request  $request
     * @return Response
     */
    public function store(Request $request)
    {
        $validator = Validator::make($request->all(), [
            'title' => 'required|unique:posts|max:255',
            'body' => 'required',
        ]);

        if ($validator->fails()) {
            return redirect('post/create')
                        ->withErrors($validator)
                        ->withInput();
        }

        // Store the blog post...
    }
}

传递给 Validator::make 方法的第一个参数是验证数据,第二个参数验证规则。

如果验证失败,你可以使用 withErrors 方法闪存错误消息到会话里。当使用这个方法时,视图里的 $errors 变量会被自动填充数据,以便之后显示。withErrors 方法接收一个验证器实例、MessageBag 或者一个 PHP 数组。

自动跳转

如果你是手动创建验证器,又想在验证失败时,自动跳转的话,请使用验证器实例上的 validate 方法。如果验证失败的话,就能自动跳转了,如果是 AJAX 请求的话,就返回一个 JSON 响应:

Validator::make($request->all(), [
    'title' => 'required|unique:posts|max:255',
    'body' => 'required',
])->validate();

命名错误包

如果一个页面里包含多个表单,可以为错误 MessageBag 命名。为 withErrors 方法传递第二个参数:

return redirect('register')
            ->withErrors($validator, 'login');

然后,就可以在 $errors 变量上使用命名的 MessageBag 实例了:

{{ $errors->loogin->first('email') }}

在验证之后的钩子函数

在验证完成之后,可以为验证器添加回调函数。这样,你可以向消息集合里为验证器进一步添加错误消息。这是要使用验证器器上的 after 方法:

$validator = Validator::make(...);

$validator->after(function ($validator) {
    if ($this->somethingElseIsInvalid()) {
        $validator->errors()->add('field', 'Something is wrong with this field!');
    }
});

if ($validator->fails()) {
    //
}

使用错误消息

Validatore 实例上调用 errors 方法,会返回一个 Illuminate\Support\MessageBag 实例,这是实例有很多操作错误消息的方法。在每个视图文件里都可取得的 $errors 变量也是 MessageBag 类实例。

获得给定字段的第一条错误消息

获得给定字段的第一条错误消息,使用 first 方法:

$errors = $validator->errors();

echo $errors->first('email');

获得给定字段的所有错误消息

获得给定字段的所有错误消息,使用 get 方法:

foreach ($errors->get('email') as $message) {
    //
}

如果你是验证一个数组表单字段,你可以使用 * 字符获得每个数组元素的所有错误消息:

foreach ($errors->get('attachments.*') as $message) {
    //
}

获得所有字段的错误消息

获得所有字段的错误消息,使用 all 方法:

foreach ($errors->all() as $message) {
    //
}

判断某个字段是否有错误消息

使用 has 方法:

if ($errors->has('email')) {
    //
}

自定义错误消息

如果需要自定义错误消息,这里有几种方法。首先,Validator::make 方法接收的可选第三个参数就是咱们的自定义消息。

$messages = [
    'required' => 'The :attribute field is required.',
];

$validator = Validator::make($input, $rules, $messages);

:attribute 占位符会被字段名替代。你也可以在验证消息里使用其他占位符:

$messages = [
    'same'    => 'The :attribute and :other must match.',
    'size'    => 'The :attribute must be exactly :size.',
    'between' => 'The :attribute value :input is not between :min - :max.',
    'in'      => 'The :attribute must be one of the following types: :values',
];

为给定的属性指定自定义消息

有时,只需要为给定的属性指定自定义消息,这时要使用 . 符号,形式是 属性名.规则

$messages = [
    'email.required' => 'We need to know your e-mail address!',
];

在语言文件里指定自定义消息

大多数情况下,你可以在语言文件里指定自定义消息,而不是直接传递给 Validator。为此,在你的语言文件 resources/lang/xx/validation.php 中的 custom 数组里添加自定义消息。

'custom' => [
    'email' => [
        'required' => 'We need to know your e-mail address!',
    ],
],

在语言文件里指定自定义属性

如果你希望使用自定义属性名称替换验证消息的 :attribute 占位符部分。为此,在你的语言文件 resources/lang/xx/validation.php 中的 attributes 数组里添加自定义属性。

'attributes' => [
    'email' => 'email address',
],


本文为转载文章: 原文链接