Laravel 的表单验证机制详解
Introduction
Laravel 提供了多种不同的验证方法来对应用程序传入的数据进行验证。默认情况下,Laravel 的基类控制器使用 ValidatesRequests
Trait,它提供了方便的方法使用各种强大的验证规则来验证传入的 HTTP 请求数据。
快速上手
为了了解 Laravel 强大验证特性,我们先来看看一个完整的表单验证并返回错误消息的示例。
定义路由
首先,我们假定在 routes/web.php
文件中定义了以下路由:
Route::get('post/create', 'PostController@create');
Route::post('post', 'PostController@store');
GET
路由会显示一个用于创建新博客文章的表单,POST
路由则会将新的博客文章保存到数据库。
创建控制器
下一步,我们来看一个处理这些路由的简单的控制器。我们将 store
方法置空:
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
class PostController extends Controller
{
/**
* 显示创建博客文章的表单。
*
* @return Response
*/
public function create()
{
return view('post.create');
}
/**
* 保存一个新的博客文章。
*
* @param Request $request
* @return Response
*/
public function store(Request $request)
{
// 验证以及保存博客发表文章...
}
}
编写验证逻辑
现在我们准备开始编写 store
逻辑方法来验证我们博客发布的新文章。检查应用程序的基底控制器 (App\Http\Controllers\Controller
) 类你会看到这个类使用了 ValidatesRequests
Trait。这个 Trait 在你所有的控制器里提供了方便的 validate
验证方法。
validate
方法会接收 HTTP 传入的请求以及验证的规则。如果验证通过,你的代码就可以正常的运行。若验证失败,则会抛出异常错误消息并自动将其返回给用户。在一般的 HTTP 请求下,都会生成一个重定向响应,而对于 AJAX 请求则会发送 JSON 响应。
让我们接着回到 store
方法来深入理解 validate
方法:
/**
* 保存一篇新的博客文章。
*
* @param Request $request
* @return Response
*/
public function store(Request $request)
{
$this->validate($request, [
'title' => 'required|unique:posts|max:255',
'body' => 'required',
]);
// 文章内容是符合规则的,存入数据库
}
如你所见,我们将本次 HTTP 请求及所需的验证规则传递至 validate
方法中。另外再提醒一次,如果验证失败,将会自动生成一个对应的响应。如果验证通过,那我们的控制器将会继续正常运行。
在第一次验证失败后停止
有时,你希望在某个属性第一次验证失败后停止运行验证规则。为了达到这个目的,附加 bail
规则到该属性:
$this->validate($request, [
'title' => 'bail|required|unique:posts|max:255',
'body' => 'required',
]);
在这个例子里,如果 title 字段 没有通过 required
的验证规则,那么 unique
这个规则将 不会被检测了。将按规则被分配的顺序来验证规则。
嵌套属性的注解
如果你的 HTTP 请求包含一个 「嵌套的」 参数,你可以在验证规则中通过 「点」 语法来指定这些参数。
$this->validate($request, [
'title' => 'required|unique:posts|max:255',
'author.name' => 'required',
'author.description' => 'required',
]);
显示验证错误
如果本次请求的参数未通过我们指定的验证规则呢?正如前面所提到的,Laravel 会自动把用户重定向到先前的位置。另外,所有的验证错误会被自动 闪存至 session。
再者,请注意在 GET
路由中,我们无需显式的将错误信息和视图绑定起来。这是因为 Lavarel 会检查在 Session 数据中的错误信息,然后如果对应的视图存在的话,自动将它们绑定起来。变量 $errors
会成为 Illuminate\Support\MessageBag
的一个实例对象。要获取关于这个对象的更多信息,请查阅这个文档。
$errors
变量被 Illuminate\View\Middleware\ShareErrorsFromSession
中间件绑定到视图,该中间件由 web
中间件组提供。当这个中间件被应用后,在你的视图中就可以获取到 $error
变量,可以使你方便的假 定 $errors
变量总是已经被定义好并且可以安全的使用。
所以,在我们的例子中,当验证失败的时候,用户将会被重定向到 create
方法,让我们在视图中显示错误信息:
<!-- /resources/views/post/create.blade.php -->
<h1>创建文章</h1>
@if (count($errors) > 0)
<div class="alert alert-danger">
<ul>
@foreach ($errors->all() as $error)
<li>{{ $error }}</li>
@endforeach
</ul>
</div>
@endif
<!-- 创建文章表单 -->
有关可选字段的注意事项
默认情况下,Laravel 会在你的应用中的全局中间件栈中包含 TrimStrings
和 ConvertEmptyStringsToNull
中间件。这些中间件在 App\Http\Kernel
类中。因此,如果您不希望验证程序将「null」值视为无效的,您通常需要将「可选」的请求字段标记为 nullable
。
$this->validate($request, [
'title' => 'required|unique:posts|max:255',
'body' => 'required',
'publish_at' => 'nullable|date',
]);
在这个例子里,我们指定 publish_at
字段可以为 null
或者一个有效的日期格式。如果 nullable
的修饰词没有添加到规则定义中,验证器会认为 null
是一个无效的日期格式。
自定义闪存的错误消息格式
当验证失败时,如果你想要在闪存上自定义验证的错误格式,则需在控制器中重写 formatValidationErrors
。别忘了将 Illuminate\Contracts\Validation\Validator
类引入到文件上方:
<?php
namespace App\Http\Controllers;
use Illuminate\Foundation\Bus\DispatchesJobs;
use Illuminate\Contracts\Validation\Validator;
use Illuminate\Routing\Controller as BaseController;
use Illuminate\Foundation\Validation\ValidatesRequests;
abstract class Controller extends BaseController
{
use DispatchesJobs, ValidatesRequests;
/**
* {@inheritdoc}
*/
protected function formatValidationErrors(Validator $validator)
{
return $validator->errors()->all();
}
}
AJAX 请求验证
在这个例子中,我们使用一种传统的方式来将数据发送到应用程序上。当我们在 AJAX 的请求中使用 validate
方法时,Laravel 并不会生成一个重定向响应,而是会生成一个包含所有错误验证的 JSON 响应。这个 JSON 响应会发送一个 422 HTTP 状态码。
表单请求验证
创建表单请求
在更复杂的验证情境中,你可能会想要创建一个「表单请求( form request )」。表单请求是一个自定义的请求类,里面包含着验证逻辑。要创建一个表单请求类,可使用 Artisan 命令行命令 make:request
:
php artisan make:request StoreBlogPost
新生成的类保存在 app/Http/Requests
目录下。如果这个目录不存在,那么将会在你运行 make:request
命令时创建出来。让我们添加一些验证规则到 rules
方法中:
/**
* 获取适用于请求的验证规则。
*
* @return array
*/
public function rules()
{
return [
'title' => 'required|unique:posts|max:255',
'body' => 'required',
];
}
怎样才能较好的运行验证规则呢?你所需要做的就是在控制器方法中利用类型提示传入请求。传入的请求会在控制器方法被调用前进行验证,意思就是说你不会因为验证逻辑而把控制器弄得一团糟:
/**
* 保存传入的博客文章。ß
*
* @param StoreBlogPost $request
* @return Response
*/
public function store(StoreBlogPost $request)
{
// The incoming request is valid...
}
如果验证失败,就会生成一个重定向响应把用户返回到先前的位置。这些错误会被闪存到 Session,所以这些错误都可以被显示。如果进来的是 AJAX 请求的话,则会传回一个 HTTP 响应,其中包含了 422 状态码和验证错误的 JSON 数据。
添加表单请求后钩子
如果你想在表单请求「之后」添加钩子,你可以使用 withValidator
方法。这个方法接收一个完整的验证类,允许你在实际判断验证规则调之前调用验证类的所有方法:
/**
* @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
方法。在 这个方法中,你可以确认用户是否真的通过了授权,以便更新指定数据。比方说,有一个用户想试图去更新一篇文章的评论,你能保证他确实是这篇评论的拥有者吗?具体代码如下:
/**
* 判断用户是否有权限做出此请求。
*
* @return bool
*/
public function authorize()
{
$comment = Comment::find($this->route('comment'));
return $comment && $this->user()->can('update', $comment);
}
由于所有的表单请求都是扩展于基础的 Laravel 请求类,所以我们可以使用 user
方法去获取当前认证登录的用户。同时请注意上述例子中对 route 方法的调用。这个方法授权你获取调用的路由规则中的 URI 参数,譬如下面例子中的
{comment}`参数:
Route::post('comment/{comment}');
如果 authorize
方法返回 false
,则会自动返回一个 HTTP 响应,其中包含 403 状态码,而你的控制器方法也将不会被运行。
如果你打算在应用程序的其它部分处理授权逻辑,只需从 authorize
方法返回 true
:
/**
* 判断用户是否有权限做出此请求。
*
* @return bool
*/
public function authorize()
{
return true;
}
自定义错误格式
如果你想要自定义验证失败时闪存到 Session 的验证错误格式,可在你的基底请求 (App\Http\Requests\Request) 中重写 formatErrors
。别忘了文件上方引入 Illuminate\Contracts\Validation\Validator
类:
/**
* {@inheritdoc}
*/
protected function formatErrors(Validator $validator)
{
return $validator->errors()->all();
}
自定义错误消息
你可以通过重写表单请求的 messages
方法来自定义错误消息。此方法必须返回一个数组,其中含有成对的属性或规则以及对应的错误消息:
/**
* 获取已定义验证规则的错误消息。
*
* @return array
*/
public function messages()
{
return [
'title.required' => 'A title is required',
'body.required' => 'A message is required',
];
}
手动创建验证请求
如果你不想要使用 ValidatesRequests
Trait 的 validate
方法,你可以手动创建一个 validator 实例并通过 Validator::make
方法在 Facade 生成一个新的 validator
实例:
<?php
namespace App\Http\Controllers;
use Validator;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
class PostController extends Controller
{
/**
* 保存一篇新的博客文章。
*
* @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();
}
// 保存文章
}
}
第一个传给 make
方法的参数是验证数据。第二个参数则是数据的验证规则。
如果请求没有通过验证,则可以使用 withErrors
方法把错误消息闪存到 Session。在进行重定向之后,$errors
变量可以在视图中自动共用,让你可以轻松地显示这些消息并返回给用户。withErrors
方法接收 validator、MessageBag,或 PHP array。
自动重定向
如果你想手动创建一个验证器实例,但希望继续享用 ValidatesRequest
特性提供的自动跳转功能,那么你可以调用一个现存的验证器实例中的 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->login->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()) {
//
}