验证
介绍
Laravel 提供了几种不同的方法来验证应用程序的传入数据。默认情况下,Laravel 的基础控制器类使用 ValidatesRequests
trait,它提供了一种方便的方法来使用各种强大的验证规则验证传入的 HTTP 请求。
验证快速入门
为了了解 Laravel 强大的验证功能,让我们看一个完整的示例,验证一个表单并将错误信息显示给用户。
定义路由
首先,假设我们在 app/Http/routes.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',
]);
// 博客文章有效,存储到数据库中...
}
如你所见,我们只需将传入的 HTTP 请求和所需的验证规则传递给 validate
方法。再次强调,如果验证失败,将自动生成适当的响应。如果验证通过,控制器将继续正常执行。
关于嵌套属性的说明
如果你的 HTTP 请求包含“嵌套”参数,可以使用“点”语法在验证规则中指定它们:
$this->validate($request, [
'title' => 'required|unique:posts|max:255',
'author.name' => 'required',
'author.description' => 'required',
]);
显示验证错误
那么,如果传入的请求参数未通过给定的验证规则怎么办?如前所述,Laravel 将自动将用户重定向回其先前的位置。此外,所有验证错误将自动 闪存到会话。
再次注意,我们不必在 GET
路由中显式绑定错误信息到视图。这是因为 Laravel 总是会检查会话数据中的错误,并在可用时自动将它们绑定到视图。因此,重要的是要注意,在每个请求中,所有视图中都将始终有一个 $errors
变量可用,允许你方便地假设 $errors
变量始终被定义并且可以安全使用。$errors
变量将是 Illuminate\Support\MessageBag
的一个实例。有关如何使用此对象的更多信息,请 查看其文档。
因此,在我们的示例中,当验证失败时,用户将被重定向到控制器的 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
<!-- 创建文章表单 -->
自定义闪存错误格式
如果你希望自定义验证失败时闪存到会话中的错误信息格式,可以在基础控制器中重写 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 状态码发送。
其他验证方法
手动创建验证器
如果你不想使用 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
{
/**
* 存储一个新的博客文章。
*
* @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
方法将错误信息闪存到会话中。使用此方法时,$errors
变量将在重定向后自动与视图共享,允许你轻松地将它们显示给用户。withErrors
方法接受一个验证器、一个 MessageBag
或一个 PHP array
。
命名错误包
如果在单个页面上有多个表单,可能希望为错误的 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()) {
//
}
表单请求验证
对于更复杂的验证场景,可能希望创建一个“表单请求”。表单请求是包含验证逻辑的自定义请求类。要创建表单请求类,请使用 make:request
Artisan CLI 命令:
php artisan make:request StoreBlogPostRequest
生成的类将放置在 app/Http/Requests
目录中。让我们在 rules
方法中添加一些验证规则:
/**
* 获取适用于请求的验证规则。
*
* @return array
*/
public function rules()
{
return [
'title' => 'required|unique:posts|max:255',
'body' => 'required',
];
}
那么,验证规则是如何评估的?只需在控制器方法中对请求进行类型提示即可。在调用控制器方法之前,将验证传入的表单请求,这意味着不需要在控制器中混杂任何验证逻辑:
/**
* 存储传入的博客文章。
*
* @param StoreBlogPostRequest $request
* @return Response
*/
public function store(StoreBlogPostRequest $request)
{
// 传入的请求是有效的...
}
如果验证失败,将生成一个重定向响应,将用户发送回其先前的位置。错误也将闪存到会话中,以便显示。如果请求是 AJAX 请求,将返回一个包含 422 状态码的 HTTP 响应,其中包括验证错误的 JSON 表示。
授权表单请求
表单请求类还包含一个 authorize
方法。在此方法中,可以检查经过身份验证的用户是否确实有权更新给定资源。例如,如果用户试图更新博客文章评论,他们是否确实拥有该评论?例如:
/**
* 确定用户是否有权发出此请求。
*
* @return bool
*/
public function authorize()
{
$commentId = $this->route('comment');
return Comment::where('id', $commentId)
->where('user_id', Auth::id())->exists();
}
注意上面示例中的 route
方法调用。此方法允许你访问正在调用的路由上定义的 URI 参数,例如示例中的 {comment}
参数:
Route::post('comment/{comment}');
如果 authorize
方法返回 false
,将自动返回一个 403 状态码的 HTTP 响应,并且控制器方法将不会执行。
如果计划在应用程序的其他部分中进行授权逻辑,只需从 authorize
方法返回 true
:
/**
* 确定用户是否有权发出此请求。
*
* @return bool
*/
public function authorize()
{
return true;
}
自定义闪存错误格式
如果希望自定义验证失败时闪存到会话中的错误信息格式,可以在基础请求 (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' => '需要一个标题',
'body.required' => '需要一条消息',
];
}
处理错误信息
在 Validator
实例上调用 errors
方法后,将收到一个 Illuminate\Support\MessageBag
实例,该实例具有多种方便的方法来处理错误信息。
检索字段的第一个错误信息
要检索给定字段的第一个错误信息,请使用 first
方法:
$messages = $validator->errors();
echo $messages->first('email');
检索字段的所有错误信息
如果只想检索给定字段的所有信息数组,请使用 get
方法:
foreach ($messages->get('email') as $message) {
//
}
检索所有字段的所有错误信息
要检索所有字段的所有信息数组,请使用 all
方法:
foreach ($messages->all() as $message) {
//
}
确定字段是否存在信息
if ($messages->has('email')) {
//
}
以格式检索错误信息
echo $messages->first('email', '<p>:message</p>');
以格式检索所有错误信息
foreach ($messages->all('<li>:message</li>') as $message) {
//
}
自定义错误信息
如果需要,可以使用自定义错误信息进行验证,而不是默认信息。有几种方法可以指定自定义信息。首先,可以将自定义信息作为第三个参数传递给 Validator::make
方法:
$messages = [
'required' => '需要 :attribute 字段。',
];
$validator = Validator::make($input, $rules, $messages);
在此示例中,:attribute
占位符将被替换为正在验证的字段的实际名称。还可以在验证信息中使用其他占位符。例如:
$messages = [
'same' => ':attribute 和 :other 必须匹配。',
'size' => ':attribute 必须正好是 :size。',
'between' => ':attribute 必须介于 :min - :max 之间。',
'in' => ':attribute 必须是以下类型之一: :values',
];
为给定属性指定自定义信息
有时可能希望仅为特定字段指定自定义错误信息。可以使用“点”符号来实现。首先指定属性的名称,然后是规则:
$messages = [
'email.required' => '我们需要知道你的电子邮件地址!',
];
在语言文件中指定自定义信息
在许多情况下,可能希望在语言文件中指定属性特定的自定义信息,而不是直接将它们传递给 Validator
。为此,请将信息添加到 resources/lang/xx/validation.php
语言文件中的 custom
数组中。
'custom' => [
'email' => [
'required' => '我们需要知道你的电子邮件地址!',
],
],
可用的验证规则
以下是所有可用验证规则及其功能的列表:
AcceptedActive URLAfter (Date)AlphaAlpha DashAlpha NumericArrayBefore (Date)BetweenBooleanConfirmedDateDate FormatDifferentDigitsDigits BetweenE-MailExists (Database)Image (File)InIntegerIP AddressJSONMaxMIME Types (File)MinNot InNumericRegular ExpressionRequiredRequired IfRequired UnlessRequired WithRequired With AllRequired WithoutRequired Without AllSameSizeStringTimezoneUnique (Database)URL
accepted
验证字段必须是 yes、on、1 或 true。这对于验证“服务条款”接受非常有用。
active_url
验证字段必须是根据 checkdnsrr
PHP 函数的有效 URL。
after:date
验证字段必须是给定日期之后的值。日期将传递给 strtotime
PHP 函数:
'start_date' => 'required|date|after:tomorrow'
而不是传递一个由 strtotime
评估的日期字符串,可以指定另一个字段来与日期进行比较:
'finish_date' => 'required|date|after:start_date'
alpha
验证字段必须完全是字母字符。
alpha_dash
验证字段可以包含字母数字字符,以及破折号和下划线。
alpha_num
验证字段必须完全是字母数字字符。
array
验证字段必须是 PHP array
。
before:date
验证字段必须是给定日期之前的值。日期将传递给 PHP strtotime
函数。
between:min,max
验证字段的大小必须介于给定的 min 和 max 之间。字符串、数字和文件的评估方式与 size
规则相同。
boolean
验证字段必须能够被转换为布尔值。接受的输入是 true
、false
、1
、0
、"1"
和 "0"
。
confirmed
验证字段必须有一个匹配的 foo_confirmation
字段。例如,如果验证字段是 password
,则输入中必须存在一个匹配的 password_confirmation
字段。
date
验证字段必须是根据 strtotime
PHP 函数的有效日期。
date_format:format
验证字段必须匹配给定的 format。格式将使用 PHP date_parse_from_format
函数进行评估。验证字段时应使用 either date
或 date_format
,而不是两者。
different:field
验证字段的值必须与 field 不同。
digits:value
验证字段必须是 numeric,并且必须具有 value 的确切长度。
digits_between:min,max
验证字段的长度必须介于给定的 min 和 max 之间。
email
验证字段必须格式化为电子邮件地址。
exists:table,column
验证字段必须存在于给定的数据库表中。
Exists 规则的基本用法
'state' => 'exists:states'
指定自定义列名
'state' => 'exists:states,abbreviation'
还可以指定更多条件,这些条件将作为“where”子句添加到查询中:
'email' => 'exists:staff,email,account_id,1'
还可以将 NULL
或 NOT_NULL
传递给“where”子句:
'email' => 'exists:staff,email,deleted_at,NULL'
'email' => 'exists:staff,email,deleted_at,NOT_NULL'
image
验证的文件必须是图像(jpeg、png、bmp、gif 或 svg)
in:foo,bar,...
验证字段必须包含在给定的值列表中。
integer
验证字段必须是整数。
ip
验证字段必须是 IP 地址。
json
验证字段必须是有效的 JSON 字符串。
max:value
验证字段必须小于或等于最大 value。字符串、数字和文件的评估方式与 size
规则相同。
mimes:foo,bar,...
验证的文件必须具有与列出的扩展名对应的 MIME 类型。
MIME 规则的基本用法
'photo' => 'mimes:jpeg,bmp,png'
即使只需指定扩展名,此规则实际上是通过读取文件的内容并猜测其 MIME 类型来验证文件的 MIME 类型。
可以在以下位置找到 MIME 类型及其对应扩展名的完整列表:http://svn.apache.org/repos/asf/httpd/httpd/trunk/docs/conf/mime.types
min:value
验证字段必须具有最小 value。字符串、数字和文件的评估方式与 size
规则相同。
not_in:foo,bar,...
验证字段不得包含在给定的值列表中。
numeric
验证字段必须是数字。
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 对应于给定的整数值。对于文件,size 对应于文件大小(以千字节为单位)。
string
验证字段必须是字符串。
timezone
验证字段必须是根据 timezone_identifiers_list
PHP 函数的有效时区标识符。
unique:table,column,except,idColumn
验证字段必须在给定的数据库表中是唯一的。如果未指定 column
选项,将使用字段名称。
指定自定义列名:
'email' => 'unique:users,email_address'
自定义数据库连接
有时,可能需要为验证器进行的数据库查询设置自定义连接。如上所示,设置 unique:users
作为验证规则将使用默认数据库连接查询数据库。要覆盖此设置,请使用“点”语法指定连接,然后是表名:
'email' => 'unique:connection.users,email_address'
强制唯一规则忽略给定 ID:
有时,可能希望在唯一检查期间忽略给定的 ID。例如,考虑一个包含用户姓名、电子邮件地址和位置的“更新个人资料”屏幕。当然,你会希望验证电子邮件地址是唯一的。然而,如果用户只更改姓名字段而不是电子邮件字段,则不希望抛出验证错误,因为用户已经是电子邮件地址的所有者。你只希望在用户提供的电子邮件地址已被其他用户使用时抛出验证错误。要告诉唯一规则忽略用户的 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
的行才会包含在唯一检查中。
url
验证字段必须是根据 PHP 的 filter_var
函数的有效 URL。
有条件地添加规则
在某些情况下,可能希望仅在输入数组中存在字段时对其运行验证检查。要快速实现此目的,请将 sometimes
规则添加到规则列表中:
$v = Validator::make($data, [
'email' => 'sometimes|required|email',
]);
在上面的示例中,只有在 $data
数组中存在 email
字段时才会对其进行验证。
复杂的条件验证
有时可能希望根据更复杂的条件逻辑添加验证规则。例如,可能希望仅在另一个字段的值大于 100 时才要求给定字段。或者,可能需要两个字段仅在另一个字段存在时具有给定值。添加这些验证规则不必是痛苦的。首先,使用从不更改的 static rules 创建一个 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
的一个实例,可以用于访问输入和文件。
自定义验证规则
Laravel 提供了多种有用的验证规则;然而,可能希望指定一些自己的规则。注册自定义验证规则的一种方法是使用 Validator
facade 上的 extend
方法。让我们在 服务提供者 中使用此方法注册一个自定义验证规则:
<?php
namespace App\Providers;
use Validator;
use Illuminate\Support\ServiceProvider;
class AppServiceProvider extends ServiceProvider
{
/**
* 启动任何应用程序服务。
*
* @return void
*/
public function boot()
{
Validator::extend('foo', function($attribute, $value, $parameters, $validator) {
return $value == 'foo';
});
}
/**
* 注册服务提供者。
*
* @return void
*/
public function register()
{
//
}
}
自定义验证器闭包接收四个参数:正在验证的 $attribute
的名称、属性的 $value
、传递给规则的 $parameters
数组和 Validator
实例。
还可以将类和方法传递给 extend
方法,而不是闭包:
Validator::extend('foo', 'FooValidator@validate');
定义错误信息
还需要为自定义规则定义错误信息。可以使用内联自定义信息数组或通过在验证语言文件中添加条目来实现。此信息应放置在数组的第一级,而不是在 custom
数组中,该数组仅用于属性特定的错误信息:
"foo" => "你的输入无效!",
"accepted" => ":attribute 必须被接受。",
// 其余的验证错误信息...
创建自定义验证规则时,有时可能需要为错误信息定义自定义占位符替换。可以通过如上所述创建自定义验证器,然后调用 Validator
facade 上的 replacer
方法来实现。可以在 服务提供者 的 boot
方法中执行此操作:
/**
* 启动任何应用程序服务。
*
* @return void
*/
public function boot()
{
Validator::extend(...);
Validator::replacer('foo', function($message, $attribute, $rule, $parameters) {
return str_replace(...);
});
}
隐式扩展
默认情况下,当正在验证的属性不存在或包含由 required
规则定义的空值时,不会运行正常的验证规则,包括自定义扩展。例如,integer
规则不会对 null
值运行:
$rules = ['count' => 'integer'];
$input = ['count' => null];
Validator::make($input, $rules)->passes(); // true
要使规则即使在属性为空时也运行,规则必须暗示该属性是必需的。要创建这样的“隐式”扩展,请使用 Validator::extendImplicit()
方法:
Validator::extendImplicit('foo', function($attribute, $value, $parameters, $validator) {
return $value == 'foo';
});
“隐式”扩展仅 暗示 属性是必需的。是否实际使缺失或空的属性无效取决于你。