背景
Laravel
提供的自動表單驗證請求類,通常一個 class
是應用到一個 Action
上的,雖說可以應用到多個 Action
上,但驗證引數很少說完全一樣,粒度太細了,如果一個 Controller
有 10 個 Action
那就得對應建立10個驗證規則類,會導致檔案太多,所以可以封裝一下 Request
,把粒度由 Action
變成 Controller
級別得粒度,這樣一個 Controller
就只用建立一個表單請求類了, 實現效果如下:
原有驗證方式
建立驗證規則
app/Http/Requests
├── Requests
│ ├── DeleteBlog.php
│ ├── StoreBlog.php
│ └── UpdateBlog.php
為了方便展示,放在了一個檔案內
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
class StoreBlogRequest extends FormRequest {
public function rules() {
return [
'title' => 'required|max:100',
'content' => 'required|max:1000'
];
}
public function messages() {return []; }
}
class UpdateBlogRequest extends FormRequest {
public function rules() {
return [
'id' => 'required|integer',
'title' => 'required|max:100',
'content' => 'required|max:1000'
];
}
public function messages() {return []; }
}
class DeleteBlogRequest extends FormRequest {
public function rules() {
return [
'id' => 'required|integer',
];
}
public function messages() {return []; }
}
使用驗證規則
app/Http/Comtrollers/PostController
namespace App\Http\Controllers;
use App\Http\Requests\DeleteBlogRequest;
use App\Http\Requests\StoreBlogRequest;
use App\Http\Requests\UpdateBlogRequest;
class PostsController
{
public function store(StoreBlogRequest $request) { /*...*/ }
public function update(UpdateBlogRequest $request) { /*...*/ }
public function delete(DeleteBlogRequest $request) { /*...*/ }
}
問題
3 個介面分別對應 StoreBlogRequest
UpdateBlogRequest
DeleteBlogRequest
,30個介面得對應30個 XxxRequest
檔案太多。
封裝
想法是一個
Controller
對應一個Request
,Request
識別需要的返回的Rules
達到的效果
class BlogController extends Controller
{
// BlogRequest 自動驗證 store 規則
public function store(BlogRequest $request) { /*...*/ }
// BlogRequest 自動驗證 update 規則
public function update(BlogRequest $request) { /*...*/ }
// BlogRequest 自動驗證 delete 規則
public function delete(BlogRequest $request) { /*...*/ }
}
實現方法
namespace App\Http\Requests;
class BlogRequest extends FormRequest
{
public function rules() {
// 獲取請求對應的ActionMethod(); e.g. store/update/delete
$actionMethod = $this->route()->getActionMethod();
if (!method_exists($this, $actionMethod)) {
return [];
}
// e.g. $this->>store();
return $this->$actionMethod();
}
public function store() {
return [
'title' => 'required|max:100',
'content' => 'required|max:1000'
];
}
public function delete() {
return [
'id' => 'required|integer',
];
}
}
這樣就可以透過定義一個Request
規則對應一個 Controller
了
但問題緊接著也來了,如果要定義自定義 message
authorize
怎麼實現呢?
根據上面的實現方式,可以抽象出一個 BaseRequest
去繼承 FormRequest
重寫對應的方法,然後自定義的 Request
再繼承 BaseRequest
專注定義驗證規則即可
BaseRequest
class BaseRequest extends FormRequest
{
public function authorize(): bool {
$actionMethod = $this->route()->getActionMethod() . 'Authorize';
if (!method_exists($this, $actionMethod)) {
return true;
}
return $this->$actionMethod();
}
public function rules(): array {
$actionMethod = $this->route()->getActionMethod() . 'Rules';
if (!method_exists($this, $actionMethod)) {
return [];
}
return $this->$actionMethod();
}
public function messages(): array {
$actionMethod = $this->route()->getActionMethod() . 'Messages';
if (!method_exists($this, $actionMethod)) {
return [];
}
return $this->$actionMethod();
}
}
可以看到,在 BaseRequest
中,方法以 ActionMethod
+ 規則
實現
BlogRequest
class BlogRequest extends BaseRequest {
public function storeRules() {
return ['title' => 'required|max:100', 'content' => 'required|max:1000'];
}
public function storeMessages() {
return ['title.required' => '標題不能為空', 'content.required' => '內容不能為空'];
}
public function updateRules() {
return ['id' => 'required|integer', 'title' => 'max:100', 'content' => 'max:1000'];
}
public function deleteRules() {
return ['id' => 'required|integer',];
}
public function deleteAuthorize() {
return false;
}
}
BolgController
class BlogController extends Controller
{
// BlogRequest 自動驗證 store 規則
public function store(BlogRequest $request) { /*...*/ }
// BlogRequest 自動驗證 update 規則
public function update(BlogRequest $request) {
return response()->json([
'status' => 200,
'message' => 'success',
],200, [], JSON_UNESCAPED_UNICODE);
}
// BlogRequest 自動驗證 delete 規則
public function delete(BlogRequest $request) { /*...*/ }
}
啟動服務驗證: php artisan serve
$ curl -s -d 'title=test' -X POST '127.0.0.1:8000/api/blog/after/store' | jq .
{
"status": 400,
"message": "內容不能為空"
}
$ curl -s -d 'id=1' -X POST '127.0.0.1:8000/api/blog/after/update' | jq .
{
"status": 200,
"message": "success"
}
$ curl -s -d 'id=1' -X POST '127.0.0.1:8000/api/blog/after/delete' | jq .
{
"status": 403,
"message": "您沒有許可權訪問" // 為什麼資訊是這個,下面會說到。
}
當然,你還可以在 BaseReqeust
中定義錯誤返回格式等
/** 引數驗證失敗返回處理 */
protected function failedValidation(Validator $validator): HttpResponseException
{
$actionMethod = $this->route()->getActionMethod() . 'FailedValidation';
// 使用自定義錯誤格式,但通常不會在具體規則類裡面重寫,因為錯誤格式應該要保持一致
// 或許需要與外部系統互動之類特殊情況就就可以重寫此方法
if (method_exists($this, $actionMethod)) {
$this->$actionMethod();
}
// 預設錯誤格式
$err = $validator->errors()->first();
throw new HttpResponseException(response()->json([
'status' => 400,
'message' => $err,
], 400, [], JSON_UNESCAPED_UNICODE));
}
請求授權驗證未透過時
/** 請求授權驗證未透過時(authorize方法 return false; 未透過時) */
protected function failedAuthorization()
{
$actionMethod = $this->route()->getActionMethod() . 'FailedAuthorization';
if (method_exists($this, $actionMethod)) {
return $this->$actionMethod();
}
throw new HttpResponseException(response()->json([
'status' => 403,
'message' => '您沒有許可權訪問',
], 403, [], JSON_UNESCAPED_UNICODE));
}
總結
- 這樣就可以實現一個
Controller
對應一個Request
了,不過有利有弊,減少了檔案數量的同時帶來的就是修改對應規則的時候需要找到對應的規則。 failedValidation
和failedAuthorization
統一返回錯誤格式也可以透過判斷他們Exception
來實現,因為它們分別丟擲的異常是ValidationException
和AuthorizationException
。- 文件只能看簡單的使用方法,遇到問題得多去上層看看原始碼,找到些另闢蹊徑的處理方法。
Code
./app/Http/Controllers
├── Controllers
│ ├── AfterBlogController.php
│ ├── BeforeBlogController.php
│ ├── ...
./app/Http/Requests
├── Requests
│ ├── BaseRequest.php
│ ├── BlogRequest.php
│ ├── DeleteBlogRequest.php
│ ├── StoreBlogRequest.php
│ └── UpdateBlogRequest.php
Route
$ php artisan route:list | grep "blog"
POST | api/blog/after/delete
POST | api/blog/after/store
POST | api/blog/after/update
POST | api/blog/before/delete
POST | api/blog/before/store
POST | api/blog/before/update
github.com
github.com/zxr615/rewrite-pay-modu...
本作品採用《CC 協議》,轉載必須註明作者和本文連結