表單請求(FormRequest)獨立驗證類完整例子
<?php
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Contracts\Validation\Validator;
use Illuminate\Http\Exceptions\HttpResponseException;
class TestRequest extends FormRequest
{
/**
* 表示驗證器是否應在第一個規則失敗時停止
* 注意此處是停止所有屬性與規則,與停止單個屬性的bail規則不同
*
* @var bool
*/
protected $stopOnFirstFailure = false;
/**
* 確定用户是否有權提出此請求
* 可以在該處做權限判斷,比如用户發表帖子,可以在此處判斷用户是否有權發帖
*
* @return bool
*/
public function authorize(): bool
{
// 例子
$registerTimestamp = strtotime(Auth::user()->created_at);
if( (time() - $registerTimestamp) < 86400 ){
//註冊時間不足24小時,禁止發表新帖子
return false;
}
return true;
}
/**
* 要驗證的數據,可省略,默認值 request()->all()
*/
public function validationData()
{
return [
'title' => '不像我只會心疼哥哥',
'content' => '哥哥,你女朋友要是知道我倆吃同一個棒棒糖,你女朋友不會吃醋吧!',
'password' => '88888888',
];
}
/**
* 驗證規則
*/
public function rules(): array
{
return [
'title' => 'required|between:8,50',
'content' => 'required|min:100',
// rule可以是|分割的字符串,也可以是一維數組
'website' => ['required', 'url'],
// 某些非內置的非通用特殊驗證需求,可以在閉包裏完成
'test' => ['size:5', function($attribute, $value, $fail){
if( $value !== '12345' ){
$fail("參數 {$attribute} 不符合要求.");
}
}]
// 假設我們為了用户安全,發帖時必須提交密碼二次驗證
// 可以使用閉包完成數據庫對比密碼,但是我們希望其他參數都驗證通過後再驗證,節省數據庫查詢,所以我們在after中進行數據庫密碼比對
'password' => 'required|between:6,20',
];
}
/**
* 自定義屬性別名,可省略
*/
public function attributes()
{
return [
'title' => '帖子標題',
'content' => '帖子內容',
];
}
/**
* 自定義錯誤消息,可省略
*/
public function messages()
{
return [
'required' => ':attribute不能為空, 請填寫後再提交',
'title.between' => '帖子標題長度限制在:min至:max個字之間',
'content.min' => '帖子內容至少需要100個字',
'password.between' => '密碼錯誤',
];
}
// 授權失敗處理, 當前類authorize()方法存在且返回 false 時調用此處 可省略
protected function failedAuthorization()
{
throw new \Illuminate\Auth\Access\AuthorizationException;
}
// 授權失敗處理, 當前類authorize()方法存在且返回 false 時調用此處 可省略
protected function failedAuthorization()
{
throw new \Illuminate\Auth\Access\AuthorizationException;
}
// 驗證器預處理
protected function prepareForValidation(): void
{
// 避免大量的自定義驗證規則通過 Validator::extend 註冊在boot中,每個請求都加載到
// 這樣寫可以僅本驗證器運行時才加載
\Illuminate\Support\Facades\Validator::extend('title', [$this, 'validateTitle']);
$this->merge([
/**
* rules驗證的是這裏你修改過後的數據
* 'www.domain.com' 變成 'http://www.domain.com' 進行驗證,且影響 $this->all() 與 $this->validated() 結果
*/
'website' => "http://".$this->website,
]);
}
// 驗證完成後對任何請求數據進行規範化 可省略
protected function passedValidation(): void
{
/**
* 'http://www.domain.com' 變成 'http://www.domain.com?code=123456'
* 影響 $this->all()、 $this->input('website') 等
* 不影響 $this->validated() 結果,裏面還是 'http://www.domain.com'
*/
$this->replace([
'website' => $this->website.'?code=123456'
]);
}
/**
* 行內驗證器
* 也許你在yii2(行內驗證器)和thinkphp(自定義驗證規則)中經常這樣做, 但是laravel中默認無法實現
* 請在 prepareForValidation 中通過 \Illuminate\Support\Facades\Validator::extend 註冊本方法後使用
*
* @return bool
*/
public function validateTitle($attribute, $value, $parameters, $validator)
{
return true;
}
/**
* 配置驗證器實例。
*
* @param \Illuminate\Validation\Validator $validator
* @return void
*/
public function withValidator(\Illuminate\Validation\Validator $validator)
{
// 這裏是驗證器的後置操作
$validator->after(function (\Illuminate\Validation\Validator $validator) {
/**
* errors 具體內容可以參考 Illuminate\Support\MessageBag
*/
if( $validator->errors()->isEmpty() ){
/**
* 運行到 isEmpty() 時,表示rules裏面的驗證規則都通過了
* 此時密碼只進行了rules裏的規則驗證,我們需要繼續驗證密碼是否與持久儲存相同
*/
$password = $this->input('password');
if( $password !== '123456' ){
$validator->errors()->add('password', '數據庫比對密碼錯誤');
}
}
});
}
/**
*
* 自定義驗證失敗後的錯誤處理
*
* 必需拋出一個Exception,否則業務代碼會繼續執行,拋出了Exception則會被框架捕捉處理
* 直接return返回一個response對象是無效的
*
* 父類 FormRequest 裏面默認拋出的是 Illuminate\Validation\ValidationException
* 如果是表單請求(application/x-www-form-urlencoded)它會重定向回來源頁面,並且把錯誤信息寫入flash session,
* 非表單請求否則返回一個固定格式的json響應
*
* 父類默認的failedValidation不能滿足以下需求
* 1、在實際使用中,有的時候我們想驗證失敗時始終返回json而不跳轉,甚至是xml
* 2、默認返回的json格式是固定,而我們可能需要自定義一些json字段
*
*/
protected function failedValidation(Validator $validator)
{
// 具體內容可以參考 Illuminate\Support\MessageBag
$error = $validator->errors();
// 自定義response對象 Illuminate\Http\Response
$response = response()->json([
//...自定義的錯誤響應內容
'code' => 10001,
'message' => $error->first(),
'errors' => $error->errors(),
]);
/**
* 拋出一個HttpResponseException異常類,
* 這將阻止驗證器調用後的後續代碼,直接發送response到瀏覽器
*/
throw new HttpResponseException($response);
}
}
驗證器錯誤揹包用法 Illuminate\Support\MessageBag
$validator = Validator::make($request->all(), [
'title' => 'required|unique:posts|max:255',
'body' => 'required',
]);
if ($validator->fails()) {
$errors = $validator->errors();
// 最常用
$errors->first($key = null, $format = null);
$errors->get($key, $format = null);
$errors->all($format = null);
$errors->isEmpty(); // bool 是否沒有錯誤
$errors->isNotEmpty(); // bool 是否包含錯誤
$errors->count(); // int 錯誤總數
$errors->add($key, $message); // this 添加錯誤
// 非常用
$errors->keys(); // 返回包含錯誤的keys
$errors->addIf($boolean, $key, $message); // 如果參數1為“true”,則將消息添加到消息包中。
$errors->isUnique($key, $message); // 確定key和message的組合是否已經存在
$errors->merge($messages); // 將新的消息數組合併到消息包中
$errors->has($key);
$errors->hasAny($keys = []); // 確定是否存在任何給定key的message,可以傳入非數組
$errors->missing($key); // 確定是否不存在所有給定key的message
$errors->unique($format = null); // 確定是否不存在所有給定key的message
$errors->forget($key);
$errors->toArray();
$errors->toJson();
}
獨立表單驗證類自動調用流程
class HomeController extends Controller{
// FormRequest代替Illuminate\Http\Request作為控制器操作的request參數注入
public function myaction(TestRequest $request)
{
// 你會發現使用獨立驗證類時,不需要fails()
// 驗證通過了
return $request->validated();
}
}
- 實現自定義驗證類
MyRequest MyRequest繼承Illuminate\Foundation\Http\FormRequestFormRequest中Trait了ValidatesWhenResolvedTraitValidatesWhenResolvedTrait的validateResolved方法調用了MyRequest->fails(), 失敗時調用failedValidation方法Illuminate\Foundation\Providers\FormRequestServiceProvider中啓用了你的MyRequest
Precognitive預認知
瀏覽器頭包含Precognition-Validate-Only時,laravel響應header自動加入Precognition-Success=true
可以使用 $this->isPrecognitive() 判斷
public function rules(): array
{
return [
'password' => ['required',
$this->isPrecognitive()
? Password::min(8)
: Password::min(8)->uncompromised(),
],
];
}