# 服務 —— 驗證
## 1、簡介
Laravel提供了多種方法來驗證應用輸入數據。默認情況下,Laravel的控制器基類使用`ValidatesRequests`?trait,該trait提供了便利的方法通過各種功能強大的驗證規則。
## 2、快速入門
要學習Laravel強大的驗證特性,讓我們先看一個完整的驗證表單并返回錯誤信息給用戶的例子。
### 2.1 定義路由
首先,我們假定在`app/Http/routes.php`文件中包含如下路由:
~~~
// 顯示創建博客文章表單...
Route::get('post/create', 'PostController@create');
// 存儲新的博客文章...
Route::post('post', 'PostController@store');
~~~
當然,GET路由為用戶顯示了一個創建新的博客文章的表單,POST路由將新的博客文章存儲到數據庫。
### 2.2 創建控制器
接下來,讓我們看一個處理這些路由的簡單控制器示例。我們先將`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)
{
// 驗證并存儲博客文章...
}
}
~~~
### 2.3 編寫驗證邏輯
現在我們準備用驗證新博客文章輸入的邏輯填充`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`方法,在強調一次,如果驗證失敗,相應的響應會自動生成。如果驗證通過,控制器將會繼續正常執行。
### 2.3.1 嵌套屬性注意事項
如果HTTP請求中包含“嵌套”參數,可以使用“.”在驗證規則中指定它們:
~~~
$this->validate($request, [
'title' => 'required|unique:posts|max:255',
'author.name' => 'required',
'author.description' => 'required',
]);
~~~
### 2.4 顯示驗證錯誤信息
那么,如果請求輸入參數沒有通過給定驗證規則怎么辦?正如前面所提到的,Laravel將會自動將用戶重定向回上一個位置。此外,所有驗證錯誤信息會自動[一次性存放到session](http://laravelacademy.org/post/230.html#ipt_kb_toc_230_5)。
注意我們并沒有在GET路由中明確綁定錯誤信息到視圖。這是因為Laravel總是從session數據中檢查錯誤信息,而且如果有的話會自動將其綁定到視圖。所以,值得注意的是每次請求的所有視圖中總是存在一個**`$errors`**變量,從而允許你在視圖中方便而又安全地使用。`$errors`變量是的一個`Illuminate\Support\MessageBag`實例。想要了解更多關于該對象的信息,[查看其文檔](http://laravelacademy.org/post/240.html#working-with-error-messages)。
所以,在我們的例子中,驗證失敗的話用戶將會被重定向到控制器的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
<!-- Create Post Form -->
~~~
### 2.5 AJAX請求&驗證
在這個例子中,我們使用傳統的表單來發送數據到應用。然而,很多應用使用AJAX請求。在AJAX請求中使用`validate`方法時,Laravel不會生成重定向響應。取而代之的,Laravel生成一個包含驗證錯誤信息的JSON響應。該JSON響應會帶上一個HTTP狀態碼`422`。
## 3、其它驗證方法
### 3.1 手動創建驗證器
如果你不想使用`ValidatesRequests`?trait的`validate`方法,可以使用`Validator`門面手動創建一個驗證器實例,該門面上的`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`方法將錯誤數據一次性存放到session,使用該方法時,`$errors`變量重定向后自動在視圖間共享,從而允許你輕松將其顯示給用戶,`withErrors`方法接收一個驗證器、或者一個MessageBag,又或者一個PHP數組。
### 3.1.1 命名錯誤包
如果你在單個頁面上有多個表單,可能需要命名MessageBag,從而允許你為指定表單獲取錯誤信息。只需要傳遞名稱作為第二個參數給`withErrors`即可:
~~~
return redirect('register')
->withErrors($validator, 'login');
~~~
然后你就可以從`$errors`變量中訪問命名的MessageBag實例:
~~~
{{ $errors->login->first('email') }}
~~~
### 3.1.2 驗證鉤子之后
驗證器允許你在驗證完成后添加回調,這種機制允許你輕松執行更多驗證,甚至添加更多錯誤信息到消息集合。使用驗證器實例上的`after`方法即可:
~~~
$validator = Validator::make(...);
$validator->after(function($validator) {
if ($this->somethingElseIsInvalid()) {
$validator->errors()->add('field', 'Something is wrong with this field!');
}
});
if ($validator->fails()) {
//
}
~~~
### 3.2 表單請求驗證
對于更復雜的驗證場景,你可能想要創建一個“表單請求”。表單請求是包含驗證邏輯的自定義請求類,要創建表單驗證類,可以使用Artisan命令`make:request`:
~~~
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){
// The incoming request is valid...
}
~~~
如果驗證失敗,重定向響應會被生成并將用戶退回上一個位置,錯誤信息也會被一次性存儲到session以便在視圖中顯示。如果是AJAX請求,帶`422`狀態碼的HTTP響應將會返回給用戶,該響應數據中還包含了JSON格式的驗證錯誤信息。
### 3.2.1 認證表單請求
表單請求類還包含了一個`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;
}
~~~
### 3.2.2 自定義一次性錯誤格式
如果你想要自定義驗證失敗時一次性存儲到session中驗證錯誤信息的格式,重寫請求基類(`App\Http\Requests\Request`)中的`formatErrors`方法即可。不要忘記在文件頂部導入`Illuminate\Contracts\Validation\Validator`類:
~~~
/**
* {@inheritdoc}
*/
protected function formatErrors(Validator $validator){
return $validator->errors()->all();
}
~~~
## 4、處理錯誤信息
調用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) {
//
}
~~~
### 4.1 自定義錯誤信息
如果需要的話,你可以使用自定義錯誤信息替代默認的,有多種方法來指定自定義信息。首先,你可以傳遞自定義信息作為第三方參數給`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',
];
~~~
### 4.1.1 為給定屬性指定自定義信息
有時候你可能只想為特定字段指定自定義錯誤信息,可以通過”.”來實現,首先指定屬性名,然后是規則:
~~~
$messages = [
'email.required' => 'We need to know your e-mail address!',
];
~~~
### 4.1.2 在語言文件中指定自定義消息
在很多案例中,你可能想要在語言文件中指定屬性特定自定義消息而不是將它們直接傳遞給`Validator`。要實現這個,添加消息到`resources/lang/xx/validation.php`語言文件的custom數組:
~~~
'custom' => [
'email' => [
'required' => 'We need to know your e-mail address!',
],
],
~~~
## 5、有效驗證規則
下面是有效規則及其函數列表:
### 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
驗證字段尺寸在給定的最小值和最大值之間,字符串、數值和文件都可以使用該規則
### boolean
驗證字段必須可以被轉化為`boolean`,接收`true`,?`false`,?`1`,`0`,?`"1"`, 和?`"0"`等輸入。
### confirmed
驗證字段必須有一個匹配字段`foo_confirmation`,例如,如果驗證字段是`password`,必須輸入一個與之匹配的`password_confirmation`字段
### date
驗證字段必須是一個基于PHP?`strtotime`函數的有效日期
### date_format:format
驗證字段必須匹配指定格式,該格式將使用PHP函數`date_parse_from_format`進行驗證。你應該在驗證字段時使用`date`或`date_format`
### different:field
驗證字段必須是一個和指定字段不同的值
### digits:value
驗證字段必須是數字且長度為`value`指定的值
### digits_between:min,max
驗證字段數值長度必須介于最小值和最大值之間
### email
驗證字段必須是格式化的電子郵件地址
### exists:table.column
驗證字段必須存在于指定數據表
**基本使用:**
~~~
'state' => 'exists:states'
~~~
**指定自定義列名:**
~~~
'state' => 'exists:states,abbreviation'
~~~
**還可以添加更多查詢條件到`where`查詢子句:**
~~~
'email' => 'exists:staff,email,account_id,1'
~~~
**傳遞NULL作為`where`子句的值將會判斷數據庫值是否為NULL:**
~~~
'email' => 'exists:staff,email,deleted_at,NULL'
~~~
### image
驗證文件必須是圖片(jpeg、png、bmp、gif或者svg)
### in:foo,bar…
驗證字段值必須在給定的列表中
### integer
驗證字段必須是整型
### ip
驗證字段必須是IP地址
### max:value
驗證字段必須小于等于最大值,和字符串、數值、文件字段的size規則一起使用
### mimes:foo,bar,…
驗證文件的MIMIE類型必須是該規則列出的擴展類型中的一個
MIMIE規則的基本使用:
~~~
'photo' => 'mimes:jpeg,bmp,png'
~~~
### min:value
驗證字段的最小值,和字符串、數值、文件字段的size規則一起使用
### not_in:foo,bar,…
驗證字段值不在給定列表中
### numeric
驗證字段必須是數值
### regex:pattern
驗證字段必須匹配給定正則表達式
> 注意:使用`regex`模式時,規則必須放在數組中,而不能使用管道分隔符,尤其是正則表達式中使用管道符號時。
### required
驗證字段時必須的
### required_if:anotherfield,value,…
驗證字段在另一個字段等于指定值value時是必須的
### required_with:foo,bar,…
驗證字段只有在任一其它指定字段存在的話才是必須的
### required_with_all:foo,bar,…
驗證字段只有在所有指定字段存在的情況下才是必須的
### required_without:foo,bar,…
驗證字段只有當任一指定字段不存在的情況下才是必須的
### required_without_all:foo,bar,…
驗證字段只有當所有指定字段不存在的情況下才是必須的
### same:field
給定字段和驗證字段必須匹配
### size:value
驗證字段必須有和給定值相value匹配的尺寸,對字符串而言,`value`是相應的字符數目;對數值而言,`value`是給定整型值;對文件而言,`value`是相應的文件字節數
### string
驗證字段必須是字符串
### timezone
驗證字符必須是基于PHP函數`timezone_identifiers_list`的有效時區標識
### unique:table,column,except,idColumn
驗證字段在給定數據表上必須是唯一的,如果不指定`column`選項,字段名將作為默認`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
~~~
**添加額外的`where`子句:**
還可以指定更多條件給`where`子句:
~~~
'email' => 'unique:users,email_address,NULL,id,account_id,1'
~~~
### url
驗證字段必須是基于PHP函數`filter_var`過濾的的有效URL
## 6、添加條件規則
在某些場景下,你可能想要只有某個字段存在的情況下運行驗證檢查,要快速完成這個,添加`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`方法的第一個參數是我們需要有條件驗證的名稱字段,第二個參數是我們想要添加的規則,如果作為第三個參數的閉包返回`true`,規則被添加。該方法讓構建復雜條件驗證變得簡單,你甚至可以一次為多個字段添加條件驗證:
~~~
$v->sometimes(['reason', 'cost'], 'required', function($input) {
return $input->games >= 100;
});
~~~
> 注意:傳遞給閉包的`$input`參數是`Illuminate\Support\Fluent`的一個實例,可用于訪問輸入和文件。
## 7、自定義驗證規則
Laravel提供了多種有用的驗證規則;然而,你可能還是想要指定一些自己的驗證規則。注冊驗證規則的一種方法是使用`Validator`[門面](http://laravelacademy.org/post/97.html)的extend方法。讓我們在[服務提供者](http://laravelacademy.org/post/91.html)中使用這種方法來注冊一個自定義的驗證規則:
~~~
<?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) {
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.",
// 驗證錯誤信息其它部分...
~~~
當創建一個自定義驗證規則時,你可能有時候需要為錯誤信息定義自定義占位符,可以通過創建自定義驗證器然后調用`Validator`門面上的`replacer`方法來實現。可以在[服務提供者](http://laravelacademy.org/post/91.html)的`boot`方法中編寫代碼:
~~~
/**
* 啟動應用服務
*
* @return void
*/
public function boot(){
Validator::extend(...);
Validator::replacer('foo', function($message, $attribute, $rule, $parameters) {
return str_replace(...);
});
}
~~~
- 前言
- 序言
- 序言 ―― 發行版本說明
- 序言 ―― 升級指南
- 序言 ―― 貢獻代碼
- 開始
- 開始 ―― 安裝及配置
- 開始 ―― Laravel Homestead
- 基礎
- 基礎 ―― HTTP路由
- 基礎 ―― HTTP 中間件
- 基礎 ―― HTTP 控制器
- 基礎 ―― HTTP 請求
- 基礎 ―― HTTP 響應
- 基礎 ―― 視圖
- 基礎 ―― Blade模板
- 架構
- 架構 ―― 一次請求的生命周期
- 架構 ―― 應用目錄結構
- 架構 ―― 服務提供者
- 架構 ―― 服務容器
- 架構 ―― 契約
- 架構 ―― 門面
- 數據庫
- 數據庫 ―― 起步
- 數據庫 ―― 查詢構建器
- 數據庫 ―― 遷移
- 數據庫 ―― 填充數據
- Eloquent ORM
- Eloquent ORM ―― 起步
- Eloquent ORM ―― 關聯關系
- Eloquent ORM ―― 集合
- Eloquent ORM ―― 調整器
- Eloquent ORM ―― 序列化
- 服務
- 服務 ―― 用戶認證
- 服務 ―― Artisan 控制臺
- 服務 ―― Laravel Cashier(交易)
- 服務 ―― 緩存
- 服務 ―― 集合
- 服務 ―― Laravel Elixir
- 服務 ―― 加密
- 服務 ―― 錯誤&日志
- 服務 ―― 事件
- 服務 ―― 文件系統/云存儲
- 服務 ―― 哈希
- 服務 ―― 幫助函數
- 服務 ―― 本地化
- 服務 ―― 郵件
- 服務 ―― 包開發
- 服務 ―― 分頁
- 服務 ―― 隊列
- 服務 ―― Redis
- 服務 ―― Session
- 服務 ―― Envoy 任務運行器(SSH任務)
- 服務 ―― 任務調度
- 服務 ―― 測試
- 服務 ―― 驗證