[TOC]
# 表单验证
## 简介
默认情况下,Laravel 的控制器基类使用`ValidatesRequests`Trait,它提供了一种方便的方法去使用各种强大的验证规则来验证传入的 HTTP 请求。
## 快速验证
### 定义路由
```
Route::get('post/create', 'PostController@create');
Route::post('post', 'PostController@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)
{
// 验证并存储博客文章...
}
}
```
### 编写验证器逻辑
WEB页面请求失败生成一个重定向响应,而 AJAX 请求则会发送 JSON 响应,JSON 响应会包含一个 HTTP 状态码 422。
```
/**
* 保存一篇新的博客文章。
*
* @param Request $request
* @return Response
*/
public function store(Request $request)
{
$validatedData = $request->validate([
'title' => 'required|unique:posts|max:255',
'body' => 'required',
]);
// 博客文章验证通过
}
```
#### 首次验证失败后停止运行
附加 bail 规则,在第一次验证失败后停止验证该属性的其他验证规则。
```
$request->validate([
'title' => 'bail|required|unique:posts|max:255',
'body' => 'required',
]);
```
#### 验证数组数据
在验证规则中通过「点」语法来指定这些参数
```
$request->validate([
'title' => 'required|unique:posts|max:255',
'author.name' => 'required',
'author.description' => 'required',
]);
```
### 显示验证错误信息
请求未通过验证,Laravel 会自动把用户重定向到之前的位置。
所有的验证错误信息会被自动存储到 session 中。
变量 $errors 是 Illuminate\\Support\\MessageBag 实例
> 注意:`$errors`变量被`Web`中间件组提供的`Illuminate\View\Middleware\ShareErrorsFromSession`中间件绑定到视图中。**当这个中间件被应用后,在你的视图中就可以获取到`$error`变量**, 可以使一直假定`$errors`变量存在并且可以安全地使用。
```
// 无需显示绑定错误,直接在重定向的模板显示 session 内的错误信息
<!-- /resources/views/post/create.blade.php -->
<h1>创建文章</h1>
@if ($errors->any())
<div class="alert alert-danger">
<ul>
@foreach ($errors->all() as $error)
<li>{{ $error }}</li>
@endforeach
</ul>
</div>
@endif
<!-- 创建文章表单 -->
```
Blade 模板方法
```
@error('title')
<span class="invalid-feedback" role="alert">
<strong>{{ $message }}</strong>
</span>
@enderror
```
### 允许 null 验证
默认情况下,在 Laravel 应用的全局中间件堆栈`App\Http\Kernel`类中包含了`TrimStrings`和`ConvertEmptyStringsToNull`中间件。因此,如果你不希望验证程序将`null`值视为无效的话,那就需要将「可选」的请求字段标记为`nullable`。
```
// 允许null值有效,添加验证规则 nullable
$request->validate([
'title' => 'required|unique:posts|max:255',
'body' => 'required',
'publish_at' => 'nullable|date',
]);
```
## 验证表单请求
### 创建表单请求验证
```
// 创建表单请求验证,生成的目录 app/Http/Requests
$ php artisan make:request StoreBlogPost
```
#### 添加验证规则
```
/**
* 获取适用于请求的验证规则。
*
* @return array
*/
public function rules()
{
return [
'title' => 'required|unique:posts|max:255',
'body' => 'required',
];
}
```
#### 在控制器中使用
```
use App\Http\Requests\StoreBlogPost;
/**
* 存储传入的博客文章。
*
* @param StoreBlogPost $request
* @return Response
*/
public function store(StoreBlogPost $request)
{
// 传入的请求通过验证...
// 获取通过验证的数据...
$validated = $request->validated();
}
```
#### 添加表单请求后钩子
允许在验证结果返回之前调用任何方法
```
/**
* 配置验证器实例。
*
* @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!');
}
});
}
```
### 表单请求授权验证
可以检查经过身份验证的用户确定其是否具有更新给定资源的权限。
```
/**
* 判断用户是否有权限做出此请求。
*
* @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;
}
```
### 自定义错误消息
```
/**
* 获取已定义验证规则的错误消息。
*
* @return array
*/
public function messages()
{
return [
'title.required' => 'A title is required',
'body.required' => 'A message is required',
];
}
```
### 自定义验证属性
```
/**
* 获取验证错误的自定义属性。
*
* @return array
*/
public function attributes()
{
return [
'email' => '邮箱地址',
];
}
```
## 手动创建验证器
通过 Validator facade 上的`make`方法手动创建验证器实例,传给`make`方法的第一个参数是需要验证的数据,第二个参数则是该数据的验证规则。
如果验证失败,则可以使用`withErrors`方法把错误消息闪存到 Session 。使用这个方法进行重定向后,`$errors`变量会自动和视图共享,你可以把这些消息显示给用户。`withErrors`方法接收验证器、`MessageBag`或 PHP`Array`。
```
<?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();
}
// Store the blog post...
}
}
```
### 自动重定向
如果验证失败,用户将会自动重定向。在 AJAX 请求中,则会返回 JSON 格式的响应。
```
Validator::make($request->all(), [
'title' => 'required|unique:posts|max:255',
'body' => 'required',
])->validate();
```
### 命名错误包
如果一个页面中有多个表单,可以通过给`withErrors`方法传递第二个参数命名错误包来检索特定表单的错误消息。
```
// 然后你就可以从`$errors`变量中获取指定表单的错误消息:
return redirect('register')
->withErrors($validator, 'login');
{{ $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`类的一个实例。
#### 查看特定字段的第一个错误信息
```
$errors = $validator->errors();
echo $errors->first('email');
```
#### 查看特定字段的所有错误消息
```
foreach ($errors->get('email') as $message) {
//
}
// 如果要验证表单的数组字段,可以使用`*`来获取每个数组元素的所有错误消息
foreach ($errors->get('attachments.*') as $message) {
//
}
```
#### 查看所有字段的所有错误消息
```
foreach ($errors->all() as $message) {
//
}
```
### 自定义错误消息
可以使用自定义错误消息取代默认值进行验证。有几种方法可以指定自定义消息。首先,你可以将自定义消息作为第三个参数传递给`Validator::make`方法:
```
// `:attribute`占位符会被验证字段的实际名称取代。
$messages = [
'required' => 'The :attribute field is required.',
];
$validator = Validator::make($input, $rules, $messages);
// 还可以在验证消息中使用其它占位符。
$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!',
];
```
#### 在语言文件中指定自定义消息
在 resources/lang/xx/validation.php 语言文件的 custom 数组中自定义消息
```
'custom' => [
'email' => [
'required' => 'We need to know your e-mail address!',
],
],
```
#### 在语言文件中指定自定义属性
在 resources/lang/xx/validation.php 语言文件的 attributes 数组自定义名称
```
'attributes' => [
'email' => 'email address',
],
```
#### 在语言文件中指定自定义值
有时可能需要将验证消息的`:value`占位符替换为值的自定义文字。
```
// 模板添加
<input type="hidden" name="payment_type" value="cc">
// 当payment type为cc时,credit card number 不能为空。
$request->validate([
'credit_card_number' => 'required_if:payment_type,cc'
]);
```
在 resources/lang/xx/validation.php 语言文件定义`values`数组
```
// 当payment type 为信用卡时,credit card number不能为空。
'values' => [
'payment_type' => [
'cc' => '信用卡'
],
],
```
## 可用验证规则
[参考文档](https://learnku.com/docs/laravel/5.8/validation/3899#c58a91)
## 按条件增加规则
#### 存在时则验证
```
// email 字段只有在 $data 数组中存在才会被验证 sometimes
$v = Validator::make($data, [
'email' => 'sometimes|required|email',
]);
// 验证始终存在但可能为空的字段 nullable
$v = Validator::make($data, [
'publish_at' => 'nullable|date',
]);
```
#### 复杂的条件验证
传入`sometimes`方法的第一个参数是要用来验证的字段名称。第二个参数是我们想使用的验证规则。`闭包`作为第三个参数传入,如果其返回`true`, 则额外的规则就会被加入。
```
$v = Validator::make($data, [
'email' => 'required|email',
'games' => 'required|numeric',
]);
// 增加基于更复杂的条件逻辑的验证规则
$v->sometimes('reason', 'required|max:500', function ($input) {
return $input->games >= 100;
});
// 对多个字段增加条件验证
$v->sometimes(['reason', 'cost'], 'required', function ($input) {
return $input->games >= 100;
});
```
> 注意:传入闭包的参数 $input 是 Illuminate\Support\Fluent 实例,可用来访问你的输入或文件对象。
## 验证数组
```
// 验证 photos[profile]
$validator = Validator::make($request->all(), [
'photos.profile' => 'required|image',
]);
// 验证指定数组输入字段中的每一个 email 是唯一的
$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',
]
],
```
## 自定义验证规则
### 使用规则对象
注册自定义验证规则,存放目录`app/Rules`
```
$ php artisan make:rule Uppercase
```
规则对象包含两个方法: passes 和 message。
passes 方法接收属性值和名称,并根据属性值是否符合规则而返回 true 或 false。
message 方法应返回验证失败时应使用的验证错误消息。
```
<?php
namespace App\Rules;
use Illuminate\Contracts\Validation\Rule;
class Uppercase implements Rule
{
/**
* 判断验证规则是否通过。
*
* @param string $attribute
* @param mixed $value
* @return bool
*/
public function passes($attribute, $value)
{
return strtoupper($value) === $value;
}
/**
* 获取验证错误消息。
*
* @return string
*/
public function message()
{
return 'The :attribute must be uppercase.';
}
}
```
或者从语言文件中返回一个错误文本
```
public function message()
{
return trans('validation.uppercase');
}
```
使用自定义验证规则
```
use App\Rules\Uppercase;
$request->validate([
'name' => ['required', 'string', new Uppercase],
]);
```
### 使用闭包
如果在应用程序中只需要一次自定义规则的功能,则可以使用闭包而不是规则对象。
```
$validator = Validator::make($request->all(), [
'title' => [
'required',
'max:255',
function ($attribute, $value, $fail) {
if ($value === 'foo') {
$fail($attribute.' is invalid.');
}
},
],
]);
```
### 使用扩展
注册自定义的验证规则的另一种方法是使用`Validator`facade 中的`extend`方法。需要在 [服务容器] 中使用这个方法来注册自定义验证规则:
```
<?php
namespace App\Providers;
use Illuminate\Support\ServiceProvider;
use Illuminate\Support\Facades\Validator;
class AppServiceProvider extends ServiceProvider
{
/**
* 引导任何应用程序。
*
* @return void
*/
public function boot()
{
/**
* $attribute:要被验证的属性名称
* $value:属性的值
* $parameters :传入验证规则的参数数组
* $validator:Validator 实列
*/
Validator::extend('foo', function ($attribute, $value, $parameters, $validator) {
return $value == 'foo';
});
}
/**
* 注册服务提供器。
*
* @return void
*/
public function register()
{
//
}
}
```
除了使用闭包,也可以传入类和方法到`extend`方法中:
```
Validator::extend('foo', 'FooValidator@validate');
```
### 定义错误消息
可以使用内联自定义消息数组或者在验证语言文件中添加条目。
错误消息应该被放到数组的第一位,而不是在只用于存放属性指定错误信息的`custom`数组内。
```
"foo" => "Your input was invalid!",
"accepted" => "The :attribute must be accepted.",
// 其余的验证错误消息...
```
为错误信息定义自定义占位符
```
/**
* 启动应用程序。
*
* @return void
*/
public function boot()
{
Validator::extend(...);
Validator::replacer('foo', function ($message, $attribute, $rule, $parameters) {
return str_replace(...);
});
}
```
### 隐式扩展
默认情况下, 当所要验证的属性不存在或包含由[`required`](https://learnku.com/docs/laravel/5.8/validation/3899#rule-required)规则定义的空值时,那么正常的验证规则,包括自定义扩展将不会执行。 例如,[`unique`](https://learnku.com/docs/laravel/5.8/validation/3899#rule-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';
});
```
- 入门指南
- 安装
- 部署
- 基础功能
- 路由
- 中间件
- CSRF 保护
- 控制器
- 请求
- 响应
- 视图
- URL
- Session
- 表单验证
- 错误
- 日志
- 前端开发
- Blade 模板
- 本地化
- 脚手架
- 编译资源 Mix
- 安全相关
- 用户认证
- API 认证
- 综合话题
- 命令行
- 广播
- 缓存
- 集合
- 事件
- 文件存储
- 辅助函数
- 邮件发送
- 消息通知
- 扩展包开发
- 队列
- 任务调度
- 数据库
- 快速入门
- 查询构造器
- 分页
- 数据库迁移
- 数据填充
- Redis
- Eloquent ORM
- 快速入门
- 速查表
- Artisan
- Auth
- Blade
- Cache
- Collection
- Composer
- Config
- Container
- Cookie
- DB
- Environment
- Event
- File
- Helper
- Input
- Lang
- Log
- Model
- Pagination
- Queue
- Redirect
- Request
- Response
- Route
- SSH
- Schema
- Security
- Session
- Storage
- String
- URL
- UnitTest
- Validation
- View