分享验证规则层的设计
今天看到了这个问题验证规则写在哪里比较好?,看了一圈下来,大家都有自己的处理方式,但是没有一个比较优雅的解决方案,我来分享一下自己的设计吧。
我们的项目使用Lumen
做API开发,如果把验证规则都写在控制器里面,不仅干扰代码结构,而且规则比较分散,增加维护成本,为了解决这个问题,我设计了Validation
层来完成所有的验证工作,下面是具体实现。
首先在Http目录下新建Validations目录存放所有的验证类:
app
├── Http
│ ├── Controllers
│ │ ├── Controller.php
│ │ └── ExampleController.php
│ └── Validations
│ └── ExampleValidation.php
ExampleController.php
和ExampleValidation.php
在命名上和目录结构上是互相对应的,它们的代码:
<?php
namespace App\Http\Controllers;
class ExampleController extends Controller
{
public function show()
{
}
public function store()
{
}
}
<?php
namespace App\Http\Validations;
class ExampleValidation
{
public function show()
{
return [
'rules' => [
'id' => 'required',
],
'message' => [
],
];
}
public function store()
{
return [
'rules' => [
'id' => 'required',
],
];
}
}
我们约定好,ExampleController
的show
、store
方法,使用ExampleValidation
中对应的show
、store
方法中返回的规则来验证,下面最重要的是能让他们按照约定关联上,我们在ExampleController
的父Controllerapp/Http/Controllers/Controller.php
中实现这个关联:
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use Laravel\Lumen\Routing\Controller as BaseController;
class Controller extends BaseController
{
public function __construct(Request $request)
{
$this->validateRequest($request);
}
protected function validateRequest(Request $request, $name = null)
{
if (! $validator = $this->getValidator($request, $name)) {
return;
}
$rules = array_get($validator, 'rules', []);
$messages = array_get($validator, 'messages', []);
$this->validate($request, $rules, $messages);
}
protected function getValidator(Request $request, $name = null)
{
list($controller, $method) = explode('@', $request->route()[1]['uses']);
$method = $name ?: $method;
$class = str_replace('Controller', 'Validation', $controller);
if (! class_exists($class) || ! method_exists($class, $method)) {
return false;
}
return call_user_func([new $class, $method]);
}
}
getValidator ()
方法会去检查有没有对应当前控制器方法的验证规则,如果有就使用该规则来验证参数,如果ExampleController
中新增了方法destroy()
需要验证规则,只需要在ExampleValidation
中添加同名的destroy()
方法,return相应的rules和messages即可。
另外,如果ExampleController
中新增了方法update()
需要复用ExampleValidation
中的store()
方法返回的规则,可以这样实现
public function update(Request $request)
{
$this->validateRequest($request, 'store');
//...
}
这个方案能在添加少量代码的情况下实现规则验证和控制器的分离,以及规则的复用,是目前我认为比较优雅的方案了,欢迎拍砖。
如果有其它好的思路,欢迎讨论。
设计的不错
挺好,个人感觉这其实与 FormRequest 比起来的话 FormRequest 更独立也更易于理解与重用。
@overtrue 按照FormRequest的定义,它的职责范围应该是处理web表单提交请求,对于API的参数验证,用它显得不太合适
ps: Lumen中没有FormRequest,所以自己换种方式实现它,并且
Validation
层也比较复符合单一职责原则http://slides.com/howtomakeaturn/model#/9/...
这种方式我觉得更棒
这个思路挺好的,根据不同的 action 来获取不同的验证规则,不知道有没有用仓储模式,https://github.com/andersao/l5-repository 这个仓储就实现了类似的功能,不过 validator 不是写在控制器中,而是写在一个额外的 validator 类里面,这样把验证从控制器中分离出来了,可以参考优化一下。
思路不错,挺好的。但是有一个问题不知道博主怎么考虑的。
就是验证完成后的返回是如何处理的呢?
如果直接在__construct中返回,不是还是会走到业务逻辑的方法中去吗?
@kaelli 几个月前的帖子都翻出来了:smile:
不是在construct返回,而是在construct中抛异常,然后在
app/Exceptions/Handler.php
handle 异常,输出response后来优化成了中间件,实现了一个完全独立的验证层了。
@song 学习了:thumbsup:
@song array_get();为什么报错?
我觉得这个验证方式比较鸡肋,FromRequest可通过注入的方式在请求层做数据验证,这个方式同样适用于API.