表单验证请求类,通常一个 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