Laravel 提供了多種方法來驗證應(yīng)用輸入數(shù)據(jù)。默認情況下,Laravel 的控制器基類使用 ValidatesRequests
trait,該 trait 提供了便利的方法通過各種功能強大的驗證規(guī)則來驗證輸入的 HTTP 請求。
要學習 Laravel 強大的驗證特性,讓我們先看一個完整的驗證表單并返回錯誤信息給用戶的例子。
首先,我們假定在 app/Http/routes.php
文件中包含如下路由:
// 顯示創(chuàng)建博客文章表單...
Route::get('post/create', 'PostController@create');
// 存儲新的博客文章...
Route::post('post', 'PostController@store');
當然,GET 路由為用戶顯示了一個創(chuàng)建新的博客文章的表單,POST 路由將新的博客文章存儲到數(shù)據(jù)庫。
接下來,讓我們看一個處理這些路由的簡單控制器示例。我們先將 store
方法留空:
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
class PostController extends Controller{
/**
* 顯示創(chuàng)建新的博客文章的表單
*
* @return Response
*/
public function create()
{
return view('post.create');
}
/**
* 存儲新的博客文章
*
* @param Request $request
* @return Response
*/
public function store(Request $request)
{
// 驗證并存儲博客文章...
}
}
現(xiàn)在我們準備用驗證新博客文章輸入的邏輯填充 store
方法。如果你檢查應(yīng)用的控制器基類(App\Http\Controllers\Controller
),你會發(fā)現(xiàn)該類使用了 ValidatesRequests
trait,這個 trait 在所有控制器中提供了一個便利的 validate
方法。
validate
方法接收一個 HTTP 請求輸入數(shù)據(jù)和驗證規(guī)則,如果驗證規(guī)則通過,代碼將會繼續(xù)往下執(zhí)行;然而,如果驗證失敗,將會拋出一個異常,相應(yīng)的錯誤響應(yīng)也會自動發(fā)送給用戶。在一個傳統(tǒng)的 HTTP 請求案例中,將會生成一個重定向響應(yīng),如果是 AJAX 請求則會返回一個 JSON 響應(yīng)。
要更好的理解 validate 方法,讓我們回到 store 方法:
/**
* 存儲博客文章
*
* @param Request $request
* @return Response
*/
public function store(Request $request){
$this->validate($request, [
'title' => 'required|unique:posts|max:255',
'body' => 'required',
]);
// 驗證通過,存儲到數(shù)據(jù)庫...
}
正如你所看到的,我們只是傳遞輸入的 HTTP 請求和期望的驗證規(guī)則到 validate
方法,在強調(diào)一次,如果驗證失敗,相應(yīng)的響應(yīng)會自動生成。如果驗證通過,控制器將會繼續(xù)正常執(zhí)行。
如果 HTTP 請求中包含“嵌套”參數(shù),可以使用“.”在驗證規(guī)則中指定它們:
$this->validate($request, [
'title' => 'required|unique:posts|max:255',
'author.name' => 'required',
'author.description' => 'required',
]);
那么,如果請求輸入?yún)?shù)沒有通過給定驗證規(guī)則怎么辦?正如前面所提到的,Laravel 將會自動將用戶重定向回上一個位置。此外,所有驗證錯誤信息會自動一次性存放到 session。
注意我們并沒有在 GET 路由中明確綁定錯誤信息到視圖。這是因為 Laravel 總是從 session 數(shù)據(jù)中檢查錯誤信息,而且如果有的話會自動將其綁定到視圖。所以,值得注意的是每次請求的所有視圖中總是存在一個$errors
變量,從而允許你在視圖中方便而又安全地使用。$errors
變量是的一個 Illuminate\Support\MessageBag
實例。想要了解更多關(guān)于該對象的信息,查看其文檔。
所以,在我們的例子中,驗證失敗的話用戶將會被重定向到控制器的 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 -->
在這個例子中,我們使用傳統(tǒng)的表單來發(fā)送數(shù)據(jù)到應(yīng)用。然而,很多應(yīng)用使用 AJAX 請求。在 AJAX 請求中使用 validate
方法時,Laravel 不會生成重定向響應(yīng)。取而代之的,Laravel 生成一個包含驗證錯誤信息的 JSON 響應(yīng)。該 JSON 響應(yīng)會帶上一個 HTTP 狀態(tài)碼422
。
如果你不想使用 ValidatesRequests
trait 的 validate
方法,可以使用 Validator
門面手動創(chuàng)建一個驗證器實例,該門面上的 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
方法的第一個參數(shù)是需要驗證的數(shù)據(jù),第二個參數(shù)是要應(yīng)用到數(shù)據(jù)上的驗證規(guī)則。
檢查請求是夠通過驗證后,可以使用 withErrors
方法將錯誤數(shù)據(jù)一次性存放到 session,使用該方法時,$errors
變量重定向后自動在視圖間共享,從而允許你輕松將其顯示給用戶,withErrors
方法接收一個驗證器、或者一個 MessageBag,又或者一個 PHP 數(shù)組。
如果你在單個頁面上有多個表單,可能需要命名 MessageBag,從而允許你為指定表單獲取錯誤信息。只需要傳遞名稱作為第二個參數(shù)給 withErrors
即可:
return redirect('register')
->withErrors($validator, 'login');
然后你就可以從$errors
變量中訪問命名的 MessageBag 實例:
{{ $errors->login->first('email') }}
驗證器允許你在驗證完成后添加回調(diào),這種機制允許你輕松執(zhí)行更多驗證,甚至添加更多錯誤信息到消息集合。使用驗證器實例上的 after
方法即可:
$validator = Validator::make(...);
$validator->after(function($validator) {
if ($this->somethingElseIsInvalid()) {
$validator->errors()->add('field', 'Something is wrong with this field!');
}
});
if ($validator->fails()) {
//
}
對于更復(fù)雜的驗證場景,你可能想要創(chuàng)建一個“表單請求”。表單請求是包含驗證邏輯的自定義請求類,要創(chuàng)建表單驗證類,可以使用 Artisan 命令 make:request
:
php artisan make:request StoreBlogPostRequest
生成的類位于 app/Http/Requests
目錄下,接下來我們添加少許驗證規(guī)則到 rules
方法:
/**
* 獲取應(yīng)用到請求的驗證規(guī)則
*
* @return array
*/
public function rules(){
return [
'title' => 'required|unique:posts|max:255',
'body' => 'required',
];
}
那么,驗證規(guī)則如何生效呢?你所要做的就是在控制器方法中類型提示該請求。表單輸入請求會在控制器方法被調(diào)用之前被驗證,這就是說你不需要將控制器和驗證邏輯雜糅在一起:
/**
* 存儲輸入的博客文章
*
* @param StoreBlogPostRequest $request
* @return Response
*/
public function store(StoreBlogPostRequest $request){
// The incoming request is valid...
}
如果驗證失敗,重定向響應(yīng)會被生成并將用戶退回上一個位置,錯誤信息也會被一次性存儲到 session 以便在視圖中顯示。如果是 AJAX 請求,帶 422
狀態(tài)碼的 HTTP 響應(yīng)將會返回給用戶,該響應(yīng)數(shù)據(jù)中還包含了 JSON 格式的驗證錯誤信息。
表單請求類還包含了一個authorize
方法,你可以檢查認證用戶是否有資格更新指定資源。例如,如果用戶嘗試更新一個博客評論,那么他是否是評論的所有者呢?舉個例子:
/**
* 判斷請求用戶是否經(jīng)過認證
*
* @return bool
*/
public function authorize(){
$commentId = $this->route('comment');
return Comment::where('id', $commentId)
->where('user_id', Auth::id())->exists();
}
注意上面這個例子中對 route
方法的調(diào)用。該方法賦予用戶訪問被調(diào)用路由 URI 參數(shù)的權(quán)限,比如下面這個例子中的{comment}
參數(shù):
Route::post('comment/{comment}');
如果 authorize
方法返回 false
,一個包含403
狀態(tài)碼的 HTTP 響應(yīng)會自動返回而且控制器方法將不會被執(zhí)行。
如果你計劃在應(yīng)用的其他部分包含認證邏輯,只需在 authorize
方法中簡單返回 true
即可:
/**
* 判斷請求用戶是否經(jīng)過認證
*
* @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();
}
調(diào)用 Validator 實例上的errors
方法之后,將會獲取一個 Illuminate\Support\MessageBag
實例,該實例中包含了多種處理錯誤信息的便利方法。
獲取某字段的第一條錯誤信息
要獲取指定字段的第一條錯誤信息,可以使用 first
方法:
$messages = $validator->errors();
echo $messages->first('email');
獲取指定字段的所有錯誤信息
如果你想要簡單獲取指定字段的所有錯誤信息數(shù)組,使用 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) {
//
}
如果需要的話,你可以使用自定義錯誤信息替代默認的,有多種方法來指定自定義信息。首先,你可以傳遞自定義信息作為第三方參數(shù)給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',
];
有時候你可能只想為特定字段指定自定義錯誤信息,可以通過”.”來實現(xiàn),首先指定屬性名,然后是規(guī)則:
$messages = [
'email.required' => 'We need to know your e-mail address!',
];
在很多案例中,你可能想要在語言文件中指定屬性特定自定義消息而不是將它們直接傳遞給 Validator
。要實現(xiàn)這個,添加消息到 resources/lang/xx/validation.php
語言文件的 custom 數(shù)組:
'custom' => [
'email' => [
'required' => 'We need to know your e-mail address!',
],
],
下面是有效規(guī)則及其函數(shù)列表:
在驗證中該字段的值必須是 yes
、on
、1
或 true
,這在“同意服務(wù)協(xié)議”時很有用。
該字段必須是一個基于 PHP 函數(shù) checkdnsrr
的有效 URL
該字段必須是給定日期后的一個值,日期將會通過 PHP 函數(shù) strtotime
傳遞:
'start_date' => 'required|date|after:tomorrow'
你可以指定另外一個比較字段而不是使用 strtotime 驗證傳遞的日期字符串:
'finish_date' => 'required|date|after:start_date'
該字段必須是字母
該字段可以包含字母和數(shù)字,以及破折號和下劃線
該字段必須是字母或數(shù)字
該字段必須是 PHP 數(shù)組
驗證字段必須是指定日期之前的一個數(shù)值,該日期將會傳遞給 PHP strtotime
函數(shù)。
驗證字段尺寸在給定的最小值和最大值之間,字符串、數(shù)值和文件都可以使用該規(guī)則
驗證字段必須可以被轉(zhuǎn)化為 boolean
,接收 true
, false
, 1
,0
,"1"
, 和 "0"
等輸入。
驗證字段必須有一個匹配字段 foo_confirmation
,例如,如果驗證字段是 password
,必須輸入一個與之匹配的 password_confirmation
字段
驗證字段必須是一個基于 PHP strtotime
函數(shù)的有效日期
驗證字段必須匹配指定格式,該格式將使用 PHP 函數(shù) date_parse_from_format
進行驗證。你應(yīng)該在驗證字段時使用 date
或 date_format
驗證字段必須是一個和指定字段不同的值
驗證字段必須是數(shù)字且長度為 value
指定的值
驗證字段數(shù)值長度必須介于最小值和最大值之間
驗證字段必須是格式化的電子郵件地址
驗證字段必須存在于指定數(shù)據(jù)表
'state' => 'exists:states'
'state' => 'exists:states,abbreviation'
還可以添加更多查詢條件到 where
查詢子句:
'email' => 'exists:staff,email,account_id,1'
'email' => 'exists:staff,email,deleted_at,NULL'
image驗證文件必須是圖片(jpeg、png、bmp、gif 或者 svg)
in:foo,bar…驗證字段值必須在給定的列表中
integer驗證字段必須是整型
ip驗證字段必須是 IP 地址
max:value驗證字段必須小于等于最大值,和字符串、數(shù)值、文件字段的 size 規(guī)則一起使用
'photo' => 'mimes:jpeg,bmp,png'
min:value 驗證字段的最小值,和字符串、數(shù)值、文件字段的 size 規(guī)則一起使用
not_in:foo,bar,… 驗證字段值不在給定列表中
numeric 驗證字段必須是數(shù)值
regex:pattern
驗證字段必須匹配給定正則表達式
注意:使用 regex
模式時,規(guī)則必須放在數(shù)組中,而不能使用管道分隔符,尤其是正則表達式中使用管道符號時。
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
是相應(yīng)的字符數(shù)目;對數(shù)值而言,value
是給定整型值;對文件而言,value
是相應(yīng)的文件字節(jié)數(shù)
string 驗證字段必須是字符串
timezone
驗證字符必須是基于 PHP 函數(shù) timezone_identifiers_list
的有效時區(qū)標識
column
選項,字段名將作為默認 column
。
指定自定義列名:'email' => 'unique:users,email_address'
unique:users
作為驗證規(guī)則將會使用默認數(shù)據(jù)庫連接來查詢數(shù)據(jù)庫。要覆蓋默認連接,在數(shù)據(jù)表名后使用”.“指定連接:'email' => 'unique:connection.users,email_address'
'email' => 'unique:users,email_address,'.$user->id
where
子句:'email' => 'unique:users,email_address,NULL,id,account_id,1'
filter_var
過濾的的有效 URL在某些場景下,你可能想要只有某個字段存在的情況下運行驗證檢查,要快速完成這個,添加 sometimes
規(guī)則到規(guī)則列表:
$v = Validator::make($data, [
'email' => 'sometimes|required|email',
]);
在上例中,email 字段只有存在于$data
數(shù)組時才會被驗證。
復(fù)雜條件驗證
有時候你可能想要基于更復(fù)雜的條件邏輯添加驗證規(guī)則。例如,你可能想要只有在另一個字段值大于 100
時才要求一個給定字段是必須的,或者,你可能需要只有當另一個字段存在時兩個字段才都有給定值。添加這個驗證規(guī)則并不是一件頭疼的事。首先,創(chuàng)建一個永遠不會改變的靜態(tài)規(guī)則到 Validator 實例:
$v = Validator::make($data, [
'email' => 'required|email',
'games' => 'required|numeric',
]);
讓我們假定我們的 web 應(yīng)用服務(wù)于游戲收集者。如果一個游戲收集者注冊了我們的應(yīng)用并擁有超過 100
個游戲,我們想要他們解釋為什么他們會有這么多游戲,例如,也許他們在運營一個游戲二手店,又或者他們只是喜歡收集。要添加這種條件,我們可以使用 Validator 實例上的 sometimes
方法:
$v->sometimes('reason', 'required|max:500', function($input) {
return $input->games >= 100;
});
傳遞給 sometimes
方法的第一個參數(shù)是我們需要有條件驗證的名稱字段,第二個參數(shù)是我們想要添加的規(guī)則,如果作為第三個參數(shù)的閉包返回true
,規(guī)則被添加。該方法讓構(gòu)建復(fù)雜條件驗證變得簡單,你甚至可以一次為多個字段添加條件驗證:
$v->sometimes(['reason', 'cost'], 'required', function($input) {
return $input->games >= 100;
});
注意:傳遞給閉包的$input
參數(shù)是 Illuminate\Support\Fluent
的一個實例,可用于訪問輸入和文件。
Laravel 提供了多種有用的驗證規(guī)則;然而,你可能還是想要指定一些自己的驗證規(guī)則。注冊驗證規(guī)則的一種方法是使用Validator
門面的 extend 方法。讓我們在服務(wù)提供者中使用這種方法來注冊一個自定義的驗證規(guī)則:
<?php
namespace App\Providers;
use Validator;
use Illuminate\Support\ServiceProvider;
class AppServiceProvider extends ServiceProvider{
/**
* 啟動應(yīng)用服務(wù)
*
* @return void
*/
public function boot()
{
Validator::extend('foo', function($attribute, $value, $parameters) {
return $value == 'foo';
});
}
/**
* 注冊服務(wù)提供者
*
* @return void
*/
public function register()
{
//
}
}
自定義驗證器閉包接收三個參數(shù):要驗證的屬性名稱,屬性值和傳遞給規(guī)則的參數(shù)數(shù)組。
你還可以傳遞類和方法到 extend
方法而不是閉包:
Validator::extend('foo', 'FooValidator@validate');
定義錯誤信息 你還需要為自定義規(guī)則定義錯誤信息。你可以使用內(nèi)聯(lián)自定義消息數(shù)組或者在驗證語言文件中添加條目來實現(xiàn)這一目的。消息應(yīng)該被放到數(shù)組的第一維,而不是在只用于存放屬性指定錯誤信息的 custom 數(shù)組內(nèi):
"foo" => "Your input was invalid!",
"accepted" => "The :attribute must be accepted.",
// 驗證錯誤信息其它部分...
當創(chuàng)建一個自定義驗證規(guī)則時,你可能有時候需要為錯誤信息定義自定義占位符,可以通過創(chuàng)建自定義驗證器然后調(diào)用Validator
門面上的replacer
方法來實現(xiàn)??梢栽诜?wù)提供者的boot
方法中編寫代碼:
/**
* 啟動應(yīng)用服務(wù)
*
* @return void
*/
public function boot(){
Validator::extend(...);
Validator::replacer('foo', function($message, $attribute, $rule, $parameters) {
return str_replace(...);
});
}