Laravel验证器最完整用法实例

表单请求(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;
    }

    // 验证器预处理 
    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);
    }
}

统一处理错误响应

<?php
namespace App\Exceptions;

use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;
use Throwable;
use Illuminate\Http\Request;
use Illuminate\Validation\ValidationException;

class Handler extends ExceptionHandler
{
    ...

    public function register(): void
    {
        // 为表单验证统一处理返回,表单验证错误将由这里兜底处理
        // 包含没有自定义 failedValidation 的独立验证类和 $request->validate()
        $this->renderable(function (ValidationException $e, Request $request) {
            return response()->json([
                'code'     => 2,
                'message'  => $e->getMessage(),
                'error'    => $e->validator->errors(),
            ], $e->status);
        });


        $this->reportable(function (Throwable $e) {
            //
        });
    }
}

验证器错误背包用法 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();
    }    

}
  1. 实现自定义验证类 MyRequest
  2. MyRequest 继承 Illuminate\Foundation\Http\FormRequest
  3. FormRequest 中Trait了 ValidatesWhenResolvedTrait
  4. ValidatesWhenResolvedTraitvalidateResolved 方法调用了 MyRequest->fails(), 失败时调用 failedValidation方法
  5. 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(),
            ],
        ];
    }
本作品采用《CC 协议》,转载必须注明作者和本文链接
本帖由系统于 1个月前 自动加精
《L01 基础入门》
我们将带你从零开发一个项目并部署到线上,本课程教授 Web 开发中专业、实用的技能,如 Git 工作流、Laravel Mix 前端工作流等。
《L04 微信小程序从零到发布》
从小程序个人账户申请开始,带你一步步进行开发一个微信小程序,直到提交微信控制台上线发布。
讨论数量: 6

不错,有些还没用到的都没了解得这么清楚 :+1:

1个月前 评论
sanders

其实也不是杂乱,手册是当你要使用的时候,翻开找用法的。所以即便是长期使用还是会回过头来用 ctrl + F 翻查手册。

楼主这里只提到的请求相关的验证,实际上我们在使用验证器的时候很多场景是脱离请求的。比如我最近做的一个关于数据导入的需求,就需要将数据行先拆分到队列任务中,再通过队列任务调用逻辑对行数据使用验证器进行校验并保存,错误则收集并导出到一份新的文件中。

个人理解:验证器的主要价值在于让你少写很多验证逻辑代码,用不用以及何时用都是开发者的自觉,但为了便于维护还是推荐使用的。

1个月前 评论
MArtian 1个月前
sanders (作者) 1个月前
MArtian 1个月前

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!