老死机带你深入理解 Laravel 之验证器下

前言

在上一篇博文《老死机带你深入理解 Laravel 之验证器上(控制器参数解析)
》,我带大家了解了Laravel是如何解析控制器方法的参数的,在今天的这篇博客中,我会给大家讲解laravel的验证器到底是如何起作用的。

准备

这篇博文是接着上篇《老死机带你深入理解 Laravel 之验证器上(控制器参数解析)
》来的,所以你必须先读这篇。

问题

在前面的分析中,我们看到Laravel会解析控制器方法的每一个参数,如果参数是类的实例,那么Laravel会通过容器创建这个参数,这个之前已经分析过了,但是你有怀疑过没有,验证器啥时候起作用的呢?因为到现在为止,我们都没看到哪个地方有调用过验证器的代码。

临时

问题就出在这里:

Laravel

容器的make方法会调用Illuminate\Container\Container类的resolve方法:

Laravel

不熟悉容器的同学,请看看官方的文档,使用起来很简单,我们进入到resolve方法中,这个方法的代码很多,但是和我们现在分析的代码有关的代码只有一行,就是下面这个哥:

Laravel

好了,在解释这个方法fireResolvingCallbacks之前,我们还需要知道一个东西,Laravel在引导的时候,注册了很多的ServiceProvider,关于ServiceProvider,请参考我之前写的这篇《博客:老司机带你深入 Laravel 之 ServiceProvider 原理》。我说这个的原因是,Laravel此时注册了一个Illuminate\Foundation\Providers\FormRequestServiceProvider的服务提供者,我们看他的boot方法:

Laravel

关于它的作用,我们先不管,我们看看容器的afterResolving方法:

Laravel

我们需要关心的是此时afterResolving方法的调用,结果存储在了容器的afterResolvingCallbacks属性中。好了,我们现在回到fireResolvingCallbacks方法中:

Laravel

我们进入到fireAfterResolvingCallbacks方法中:

Laravel

这里的getCallbacksForType方法被调用:

Laravel

对于我们的DebugFormRequest来说,我们看它的定义:

Laravel

Illuminate\Foundation\Http\FormRequest的定义如下:

Laravel

Illuminate\Foundation\Http\FormRequest实现了ValidatesWhenResolved接口,还记得Illuminate\Foundation\Providers\FormRequestServiceProvider中的boot方法注册了一个ValidatesWhenResolved::class,我再给大家贴出来:

Laravel

回到容器的getCallbacksForType方法中,因为我们的DebugFormRequest实现了ValidatesWhenResolved::class这个接口,所以它会被返回给调用者,返回到fireAfterResolvingCallbacks方法中,继续调用fireCallbackArray方法:

Laravel

这段代码就很简单了,遍历所有符合条件的回调方法,这里我们的回调就是ValidatesWhenResolved::class注册的,我们看它的回调方法,参数$resolvedDebugFormRequest类的对象,进入到回调方法内部,调用DebugFormRequest类的validate方法,DebugFormRequest本身,包括它的父类都没有实现validate方法,但是DebugFormRequest父类Illuminate\Foundation\Http\FormRequest使用到了ValidatesWhenResolvedTrait这个trait,如下所示:

临时

ValidatesWhenResolvedTrait实现了validate方法,如下:

临时

我们进入到validate方法中,继续分析代码,prepareForValidation方法被设计为protected,所以在当前类中是空的,子类可以实现它,做任何操作,接下来调用getValidatorInstance方法,这个方法获取一个验证器实例,这个方法被Illuminate\Foundation\Http\FormRequest所覆盖,我们看:

临时

$this->container->make(ValidationFactory::class)返回的是Illuminate\Validation\Factory类的对象,关于getValidatorInstance方法的代码,我只分析默认验证器的情况(另外一种可以自行分析):

临时

首先validationData方法被调用,这个方法很简单,直接调用$this->all(),这个方法是哪里来的呢?我们知道FormRequest继承自Illuminate\Http\RequestIlluminate\Http\Request的定义如下所示:

临时

Illuminate\Http\Request使用到了多个trait,这其中就包括Concerns\InteractsWithInputall()方法就是它实现的,我并不打算分析这个方法,你只需要知道这个方法返回当前请求的所有数据(),无论是post请求还是get请求。

我们返回到Illuminate\Foundation\Http\FormRequest类的createDefaultValidator方法中,调用$this->container->call([$this, 'rules'])方法,rules方法就是我们自己实现的方法,这个大家应该都知道的,$this->messages()方法我们自己也是可以实现的,自定义错误提示信息,$this->attributes()方法的作用,如果你不清楚,请查看这个链接:Customizing The Validation Attributes,不过除了rules方法外,其它自定义方法都不是我们分析的重点。

createDefaultValidator方法的最后调用Illuminate\Validation\Factory类的make方法:

临时

这个方法中,只有一行代码是关键,resolve方法被调用:

临时

这里直接返回一个Illuminate\Validation\Validator类的对象,好了,经过上面的分析,我们从Illuminate\Foundation\Http\FormRequest类的createDefaultValidator方法中返回,回到当前类的getValidatorInstance方法,调用如下代码:

临时

这里检查我们是否实现了withValidator方法,你可以在这里做一些你自己的验证,关于这些细节问题,以后有时间再说,我现在这里贴出Laravel官网对它的解释和用法:

临时

经过上面的分析,我们就可以从getValidatorInstance方法返回到ValidatesWhenResolvedTraitvalidate方法中:

临时

调用passesAuthorization方法,类FormRequest实现了这个方法:

临时

这个方法返回一个bool值,表示是否通过验证,比如我的代码:

临时

返回到ValidatesWhenResolvedTraitvalidate方法中,最关键的代码是$instance->passes()方法,这里调用我们之前创建的Illuminate\Validation\Validator类的passes方法,这是整个验证最关键的部分:

临时

这里的代码很简单,就是遍历所有的验证规则,检查是否通过验证,关于规则如何验证的,留给大家自己分析吧,这个很简单。

临时

Illuminate\Validation\Validator类的passes方法中返回之后,我们再一次回到ValidatesWhenResolvedTraitvalidate方法中,如果刚才的验证失败的话,那么failedValidation方法会被调用,FormRequest类实现了这个方法,我们来看:

临时

这里直接抛出了一个Illuminate\Validation\ValidationException类的异常,那么你可能会好奇,这个异常怎么处理的呢?下面我简要的给大家分析哈(具体的后面会有专门的章节讲解),看下图:

临时

我们项目的根目录下面的app/Exceptions目录下有个Handler.php文件,这个文件就是用来处理异常的,这个类继承自Illuminate\Foundation\Exceptions\Handler类,这个类的render方法,如下所示:

临时

这个异常的类型和验证器抛出的异常类型是一样的,方法convertValidationExceptionToResponse也很简单:

临时

expectsJson检查当前是不是Ajax请求,如果是的话,调用invalidJson方法:

临时

总结

该文是验证器原理系列的第二篇,希望你能够理解,诸多细节,需要靠你自己去挖掘,如果你看不懂,请联系我,交流是成长的必要手段,在下有个QQ群,有兴趣可以加下:

本作品采用《CC 协议》,转载必须注明作者和本文链接
微信:okayGoHome
本帖由系统于 4年前 自动加精
Dennis_Ritchie
《L04 微信小程序从零到发布》
从小程序个人账户申请开始,带你一步步进行开发一个微信小程序,直到提交微信控制台上线发布。
《G01 Go 实战入门》
从零开始带你一步步开发一个 Go 博客项目,让你在最短的时间内学会使用 Go 进行编码。项目结构很大程度上参考了 Laravel。
讨论数量: 2
黑将军

为啥我会看成《老司机带你死在Laravel验证器之下》,原来标题错了,我眼睛也花了 :joy:

4年前 评论
Dennis_Ritchie (楼主) 4年前

感谢老司机的文章.mark后看

4年前 评论

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