Controller中做表单验证

有的同学把表单验证逻辑写在Controller中,例如这个对用户提交评论内容的验证:

 public function postStoreComment(Request $request)
    {
        $validator = Validator::make($request->all(), [
            'comment' => 'required', // 只是实例,就写个简单的规则,你的网站要是这么写欢迎在评论里贴网址
        ]);

        if ($validator->fails()) {
            return redirect()
                ->back()
                ->withErrors($validator)
                ->withInput();
        }
    }

这样写的话,表单验证和业务逻辑挤在一起,我们的Controller中就会有太多的代码,而且重复的验证规则基本也是复制粘贴。 我们可以利用Form Request来封装表单验证代码,从而精简Controller中的代码逻辑,使其专注于业务。而独立出去的表单验证逻辑甚至可以复用到其它请求中,例如修改评论。

什么是Form Request 在Laravel中,每一个请求都会被封装为一个Request对象,Form Request对象就是包含了额外验证逻辑(以及访问权限控制)的自定义Request类。

如何使用Form Request做表单验证 Laravel提供了生成Form Request的Artisan命令:

$ php artisan make:request SubmitFormRequest

于是就生成了app/Http/Requests/SubmitFormRequest.php,让我们来分析一下内容:

namespace App\Http\Requests;

use App\Http\Requests\Request; // 可以看到,这个基类是在我们的项目中的,这意味着我们可以修改它

class SubmitFormRequest extends Request
{
    /**
     * Determine if the user is authorized to make this request.
     *
     * @return bool
     */
    public function authorize() // 这个方法可以用来控制访问权限,例如禁止未付费用户评论…
    {
        return false; // 注意!这里默认是false,记得改成true
    }

    /**
     * Get the validation rules that apply to the request.
     *
     * @return array
     */
    public function rules() // 这个方法返回验证规则数组,也就是Validator的验证规则
    {
        return [
            //
        ];
    }

authorize() 方法用于检查用户权限,如果返回 false 则表示用户无权提交表单,会抛出权限异常中止请求,现在我们将其调整为返回 true 即可,然后我们在 rules() 方法中定义请求字段验证规则,比如我们可以将上一篇教程中的字段验证规则移到该方法中:

public function rules()
{
    return [
        'title' => 'bail|required|string|between:2,32',
        'url' => 'sometimes|url|max:200',
        'picture' => 'nullable|string'
    ];
}

然后你可能要问那自定义错误提示消息在哪里定义呢?既然是在类中,自然可以通过方法来实现,我们只需重写父类的 messages() 方法即可:

public function messages()
{
    return [
        'title.required' => '标题字段不能为空',
        'title.string' => '标题字段仅支持字符串',
        'title.between' => '标题长度必须介于2-32之间',
        'url.url' => 'URL格式不正确,请输入有效的URL',
        'url.max' => 'URL长度不能超过200',
    ];
}

这样,我们就将控制器方法中的表单请求字段验证逻辑全部迁移过来了。

表单请求类的执行

接下来,问题又来了,这段表单请求字段验证逻辑放在哪里执行呢?答案是将其以类型提示的方式注入到请求路由对应的控制器方法即可,在本例中,就是 IndexController 的 post 方法:

use App\Http\Requests\SubmitFormRequest;
class IndexController extends Controller
{
    public function post(SubmitFormRequest $request){
		
			//验证通过了,继续执行
        return $request->input('title');
    }
}

这样Laravel便会自动调用SubmitFormRequest进行表单验证了。

异常处理 如果表单验证失败,Laravel会重定向到之前的页面,并且将错误写到Session中,如果是AJAX请求,则会返回一段HTTP状态为422的JSON数据,类似这样:

{comment: ["The comment field is required."]}

但是我们在开发API的时候,我们需要在表单验证失败时,抛出异常,响应错误信息给前端。这样我们需要自定义错误处理。

一个可行的方法是,在SubmitFormRequest类中重写父类\Illuminate\Foundation\Http\FormRequest方法failedValidation

protected function failedValidation(Validator $validator)
    {
        $error= $validator->errors()->all();
        throw new HttpResponseException($this->fail(400, $error));
    }

    protected function fail(int $code, array $errors) : JsonResponse
    {
        return response()->json(
            [
                'code' => $code,
                'errors' => $errors,
            ]
        );
    }

这样,如果验证失败,那么返回信息,类似于

{
    "code": 400,
    "errors": [
        "标题长度必须介于2-5之间"
    ]
}

整个SubmitFormRequest.php如下;

namespace App\Http\Requests;

use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Http\JsonResponse;
use Illuminate\Contracts\Validation\Validator;
use Illuminate\Http\Exceptions\HttpResponseException;


class SubmitFormRequest extends FormRequest
{
    /**
     * Determine if the user is authorized to make this request.
     *
     * @return bool
     */
    public function authorize()
    {
        return true;
    }

    /**
     * Get the validation rules that apply to the request.
     *
     * @return array
     */
    public function rules()
    {
        return [
            'title' => 'bail|required|string|between:2,5',
            'url' => 'sometimes|url|max:200',
            'picture' => 'nullable|string'
        ];
    }

    public function messages()
    {
        return [
            'title.required' => '标题字段不能为空',
            'title.string' => '标题字段仅支持字符串',
            'title.between' => '标题长度必须介于2-5之间',
            'url.url' => 'URL格式不正确,请输入有效的URL',
            'url.max' => 'URL长度不能超过200',
        ];
    }

    protected function failedValidation(Validator $validator)
    {
        $error= $validator->errors()->all();
        throw new HttpResponseException($this->fail(400, $error));
    }

    protected function fail(int $code, array $errors) : JsonResponse
    {
        return response()->json(
            [
                'code' => $code,
                'errors' => $errors,
            ]
        );
    }

}