简介
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 响应。
要更好的理解 validate
方法,让我们返回 store
方法:
/** * 存储新文章 * * @param Request $request * @return Response */ public function store(Request $request) { $this->validate($request, [ 'title' => 'required|unique:posts|max:255', 'body' => 'required', ]); // 文章验证通过,存储到数据库 }
你可以看到,我们只给 validate
方法传递了进入的 HTTP 请求及期望的验证规则。再次强调,如果验证失败,将会自动生成一个合适的响应。如果验证通过,则控制器会继续正常执行。
首次验证失败后终止验证
有时候你可能希望验证失败后终止该属性的后续验证,我们可以对该属性使用 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
路由中显式的把错误信息绑定到视图。这是因为 Laravel 会检查 session 数据中的错误信息,如果可用,则自动把它们绑定到视图。$errors
变量是 Illuminate\Support\MessageBag
的一个实例。要了解这个对象的更多内容,请查看它的文档。
$errors
变量是通过 Illuminate\View\Middleware\ShareErrorsFromSession
中间件绑定到视图的,该中间件提供了一个 web 中间件组。它提供的 $errors
变量在你的视图中永远都是可用的,允许你假定 $errors
永远是定义了的,并且可以安全的使用。所以,在我们的例子中,当验证失败时用户将会被重定向到控制器的 create
方法,并允许我们在视图中展示错误信息:
<!-- /resources/views/post/create.blade.php --> <h1>Create Post</h1> @if (count($errors) > 0) <div class="alert alert-danger"> <ul> @foreach ($errors->all() as $error) <li>{{ $error }}</li> @endforeach </ul> </div> @endif <!-- 创建文章表单 -->
自定义闪存错误信息格式
如果你想在验证失败时自定义闪存到 session 中的验证错误信息,在基类控制器中重写 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 请求。当在 AJAX 请求中使用 validate
方法时,Laravel 不会生成重定向响应。相反,Laravel 会生成一个包含所有验证错误信息的 JSON 响应。JSON 响应会发送一个 422 HTTP 状态码。
表单请求验证
创建表单请求
对于更复杂的验证情形,你可能希望创建一个“表单请求”。表单请求是包含验证逻辑的自定义请求类。要创建表单请求类,可以使用 make:request
Artisan 命令:
php artisan make:request StoreBlogPost
生成的类会放在 app/Http/Requests
文件夹。如果这个文件夹不存在,则在执行 make:reques
命令时会自动创建。我们来为 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) { // 进入的请求已经通过验证 }
如果验证失败,则重定向响应会把用户重定向到前一个位置。错误信息也会闪存到 session 以便展示。如果是 AJAX 请求,则会返回一个 422 状态码的 HTTP 响应,包含了JSON格式的验证错误信息。
表单请求授权
表单请求还包含了一个 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
,将会自动返回一个 403 状态的 HTTP 响应,并且控制器方法不会被执行。
如果你准备在应用的其他地方进行授权逻辑,只需在 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
facade 手动创建一个验证器实例。该 facade 的 make
方法会生成一个信息验证器实例:
<?php namespace App\Http\Controllers; use Validator; use Illuminate\Http\Request; use App\Http\Controllers\Controller; class PostController 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... } }
传递给 make
的第一个参数是需要验证的数据,第二个参数是需要应用到数据上的验证规则。
当检查请求是否通过验证后,你可以使用 withErrors
方法把错误信息闪存到 session 中。当使用这个方法时,$errors
变量将自动共享给你重定向后的视图,允许你轻松的把它们展示给用户。withErrors
方法接收一个验证器、一个 MessageBag
、或一个 PHP 数组。
自动重定向
如果你想手动创建一个验证器实例,并且还想利用 ValidatesRequest
trait 提供的自动重定向的便利,你可以在验证器实例后调用 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()) { // }
处理错误信息
当调用 Validator
实例的 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 must be 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!', ], ],
可用的验证规则
下面是验证规则的列表及它们的方法:
accepted
这个字段必须为 yes 、on、1、true。这个规则在“同意服务协议”情形下很有用。
active_url
这个验证字段必须是通过 PHP checkdnsrr
函数的有效 URL。
after:date
这个验证字段必须为一个给定日期之后的值。这个值会被传递给 PHP 的 strtotime
函数:
'start_date' => 'required|date|after:tomorrow'
除了传递一个用于 strtotime
计算的日期字符串,你还可以传递一个作比较的日期字段:
'finish_date' => 'required|date|after:start_date'
alpha
这个字段必须全部是字母字符。
alpha_dash
这个字段可以是字母和数字字符,以及破折号和下划线。
alpha_num
验证字段必须全部是字母和数字字符。
array
验证字段必须是一个 PHP 数组。
before:date
验证字段必须是先于给定日期的值。日期将会传递给 PHP strtotime
函数。
between:min,max
验证字段必须为介于给定的 min 和 max 之间的大小。字符串、数字、文件都会按照 size
规则进行计算。
boolean
验证字段必须可以作为布尔值。接受输入为 true
、false
、1
、0
、"1"
、"0"
。
confirmed
验证字段必须有一个匹配的 foo_confirmation
字段。例如,验证的字段是 password
,那么输入中也必须设置 password_confirmation
字段。
date
验证字段必须是基于 PHP strtotime
函数的有效日期。
date_format:format
验证字段必须匹配给定的 format 。格式必须通过 PHP 的 date_parse_from_format
函数计算。验证字段中你可以使用 date
或 date_format
,但不能用两个。
different:field
验证字段必须与 field 不同。
digits:value
验证字段必须为数字,且必须为给定的长度。
digits_between:min,max
验证字段的长度必须在给定的 min 和 max 之间。
dimensions
验证字段必须为图片且尺寸限制必须符合规则参数给定的值:
'avatar' => 'dimensions:min_width=100,min_height=200'
可用的限制有:min_width, max_width, min_height, max_height, width,height, ratio。
ratio 限制必须是宽度/高度,既可以是 3/2
形式,也可以是 1.5
这样的浮点数:
'avatar' => 'dimensions:ratio=3/2'
distinct
当处理数组时,验证字段必须不能包含相同的值:
'foo.*.id' => 'distinct'
验证字段必须符合邮件地址格式。
exists:table,column
验证字段必须存在于给定的数据库中。
exists 规则基本用法
'state' => 'exists:states'
指定列名
'state' => 'exists:states,abbreviation'
你还可以添加更多条件作为 where 语句的查询:
'email' => 'exists:staff,email,account_id,1'
这些条件还可以使用 !
来取反:
'email' => 'exists:staff,email,role,!admin'
你还可以给 where 条件传递 NULL
和 NOT_NULL
:
'email' => 'exists:staff,email,deleted_at,NULL' 'email' => 'exists:staff,email,deleted_at,NOT_NULL'
有时候你可能需要指定一个具体的数据库连接用于 exists
查询。你可以通过使用“点”语法来实现:
'email' => 'exists:connection.staff,email'
file
验证字段必须为一个成功上传的文件。
filled
验证字段设置后必须不能为空。
image
验证字段必须为图片(jpeg, png, bmp, gif, or svg)。
in:foo,bar,…
验证字段必须在给定值的列表中。
in_array:anotherfield
验证字段必须存在于 anotherfield 中。
integer
验证字段必须为一个整数。
ip
验证字段必须为一个 IP 地址。
json
验证字段必须为一个合法的 JSON 字符串。
max:value
验证字段必须小于等于给定的最大值。字符串、数字、文件都是有 size
规则计算。
mimetypes:text/plain,…
验证文件必须匹配给定的 MIME 类型中的一个:
'video' => 'mimetypes:video/avi,video/mpeg,video/quicktime'
要检查上传文件的 MIME 类型,会读取文件的内容,框架会试图猜测 MIME 类型,可能与客户端提供的 MIME 类型不同。
mimes:foo,bar,…
验证字段的 MIME 类型必须与列出的扩展中的一个相符:
Mimes 规则基本用法
'photo' => 'mimes:jpeg,bmp,png'
即使你需要指定扩展,实际的验证规则会通过读取文件内容来猜测它的 MIME 类型。
全部的 MIME 类型及它们的扩展可以通过下面的链接找到:
http://svn.apache.org/repos/asf/httpd/httpd/trunk/docs/conf/mime.types
min:value
验证字段必须有个最小值。字符串、数字、文件都是有 size
规则计算。
nullable
验证字段可以是 null
。这在字符串或数字可以为 null
时非常有用。
not_in:foo,bar,…
验证字段必须不能包含在给定值的列表中。
numeric
验证字段必须为数字。
present
验证字段在输入数据中必须设置,但可以为空。
regex:pattern
验证字段必须符合给定的正则表达式。
注:使用 regex
规则时,使用数组来代替 “|
” 是非常必要的,尤其是当正则表达式包含一个 “|
” 时。
required
验证字段在输入数据中必须设置且不能为空。如果下面的条件是真时,字段会被当做“空”:
- 值是
null
。 - 值是空字符串。
- 值是空数组或空
Countable
对象。 - 值是一个没有路径的上传文件。
required_if:anotherfield,value,…
验证字段在 anotherfield 等于 value 时必须设置且不能为空。
required_unless:anotherfield,value,…
除非 anotherfield 等于 value,否则验证字段必须设置且不能为空。
required_with:foo,bar,…
只有当给定的其他字段其中一个设置时,验证字段才必须设置且不能为空。
required_with_all:foo,bar,…
只有当所有其他字段都设置时,验证字段才必须设置且不能为空。
required_without:foo,bar,…
只有当其他字段中的一个未设置时,验证字段才必须设置且不能为空。
required_without_all:foo,bar,…
其他字段都未设置时,验证字段才必须设置且不能为空。
same:field
验证字段必须与field 匹配。
size:value
验证字段必须与给定的 value 大小匹配。对于字符数据,字符个数必须与 value 相等。对数字数据,value 必须为给定的整数值。对于数组,与数组的 count
结果相等。对于文件,与文件大小的 KB 相符。
string
验证字段必须为字符串。如果你允许字段为 null
,你需要为字段指定 nullable
规则。
timezone
验证字段必须是与 PHP timezone_identifiers_list
函数一致的时区标识符。
unique:table,column,except,idColumn
验证字段在给定数据库中必须唯一。
指定自定义列名
'email' => 'unique:users,email_address'
有时候你可能需要为数据库查询自定义连接。正如上面所见,设置 unique:users
验证规则会使用默认的数据库连接,要覆盖这个,请使用“点”语法来指定连接及表名:
'email' => 'unique:connection.users,email_address'
强制 uinque 规则忽略给定的 ID
有时候,使用 unique
时你需要忽略一个给定的 ID,例如,在一个有用户名、邮件、位置的“更新简介”页面。当然,你想检查邮件是唯一的。然而,用户只想更新用户名而非邮件,由于用户已经是这个邮件地址的拥有者,你不希望抛出验证错误。要告诉 unique
规则忽略这个用户的 ID,你可以把用户 ID 作为第三个参数:
'email' => 'unique:users,email_address,'.$user->id
如果你数据库使用 id
外的其他主键,你需要在第四个参数中指定它:
'email' => 'unique:users,email_address,'.$user->id.',user_id'
添加额外的 where 条件
你还可以添加额外的条件作为 where 子句的查询:
'email' => 'unique:users,email_address,NULL,id,account_id,1'
上面的例子中,只有 account_id
为 1
时才会使用 unique
规则。
当使用 Eloquent 模型的“软删除”时,这个规则尤其有用。例如,你可以验证 deleted_at
是 NULL
:
'email' => 'unique:users,email,NULL,id,deleted_at,NULL'
url
验证字段必须是一个 URL。
添加条件规则
当设置时验证
在某些情形下,你可能希望只有字段在输入数组中设置时才运行验证规则,要实现这个,你可以把 sometimes
添加到你的规则中:
$v = Validator::make($data, [ 'email' => 'sometimes|required|email', ]);
在上面的例子中,只有当 email
在 $data
中设置时才会进行验证。
复杂条件验证
有时候你可能希望基于更复杂的验证逻辑添加验证规则。例如,你希望另一个字段大于 100 时这个字段才必须有。或者,只有当另一个字段设置时,这两个字段才必须为给定的值。添加这样的验证规则不再是一件头疼的事。首先,创建一个永远不变的静态的 Validator
实例:
$v = Validator::make($data, [ 'email' => 'required|email', 'games' => 'required|numeric', ]);
假设我们是一个游戏收藏的 WEB 应用。假设一个游戏收藏者注册了我们的应用,且他有 100 多个游戏,我们希望他们解释为什么他们有这么多游戏。例如,他们运营一个游戏专卖商店,或者他们只是喜欢收藏。要添加这个条件验证,我们可以使用 Validator
实例的 sometimes
方法:
$v->sometimes('reason', 'required|max:500', function($input) { return $input->games >= 100; });
传递给 sometimes
方法的第一个参数为我们希望验证的字段,第二个参数是我们希望添加的验证规则,如果传递的第三个 Closure
返回 true
,这个规则将会添加。这个方法使得创建复杂的条件验证变成一件轻而易举的事。你还可以一次为多个字段添加额外的规则:
$v->sometimes(['reason', 'cost'], 'required', function($input) { return $input->games >= 100; });
传递给 Closure
的 $input
参数是 Illuminate\Support\Fluent
的一个实例,并且可以用来访问你的输入数据。
验证数组
基于表单输入的数组验证也不再是一件痛苦的事。例如,要验证给定的数组数入字段中每一个邮件地址都是唯一的,你可以这么做:
$validator = Validator::make($request->all(), [ 'person.*.email' => 'email|unique:users', 'person.*.first_name' => 'required_with:person.*.last_name', ]);
同样的,当在语言文件中指定验证信息时你也可以使用 *
字符,使得一个验证信息可以为数组字段使用:
'custom' => [ 'person.*.email' => [ 'unique' => 'Each person must have a unique e-mail address', ] ],
自定义验证规则
Laravel 提供了多种有用的验证规则;然而,你可能希望指定你自己的。其中一个注册自定义验证规定的方法是使用 Validator
facade 的 extend
方法。让我们在服务容器中使用该方法来注册一个自定义规则:
<?php namespace App\Providers; use Illuminate\Support\ServiceProvider; use Illuminate\Support\Facades\Validator; class AppServiceProvider extends ServiceProvider { /** * Bootstrap any application services. * * @return void */ public function boot() { Validator::extend('foo', function($attribute, $value, $parameters, $validator) { return $value == 'foo'; }); } /** * Register the service provider. * * @return void */ public function register() { // } }
自定义的验证器闭包接收四个参数:要验证的 $attribute
名,属性的 $value
,传递给规则的 $parameters
数组,以及一个 Validator
实例。
你还可以给 extend
方法传递一个类和方法,而非闭包:
Validator::extend('foo', 'FooValidator@validate');
定义错误信息
你还需要为你的自定义规则定义错误信息。你可以通过一个行内的自定义信息数组或添加一个完整的验证语言文件来实现。这些信息应该作为数组的第一层,而非在 custom
数组(只用于属性指定的错误信息)中:
"foo" => "Your input was invalid!", "accepted" => "The :attribute must be accepted.", // 剩下的验证错误信息
创建自定义验证规则时,有时你需要为错误验证信息自定义占位符。你可以像上面那样创建一个自定义验证器,然后调用 Validator
facade 的 replacer
方法。你可以在服务容器的 boot
方法中来实现:
/** * Bootstrap any application services. * * @return void */ public function boot() { Validator::extend(...); Validator::replacer('foo', function($message, $attribute, $rule, $parameters) { return str_replace(...); }); }
隐式扩展
默认情况下,被验证的属性如果没有提供或者验证规则为 required
而值为空,那么正常的验证规则,包括自定义扩展将不会执行。例如,unique
规则将不会检验 null
值:
$rules = ['name' => 'unique']; $input = ['name' => null]; Validator::make($input, $rules)->passes(); // true
即使属性为空也要允许验证规则,规则必须暗示属性是必须的。要创建这样一个“隐式”的扩展,可以使用 Validator::extendImplicit()
方法:
Validator::extendImplicit('foo', function($attribute, $value, $parameters, $validator) { return $value == 'foo'; });
该篇属于专题:《Laravel 5.3 中文文档》