老死机带你深入理解 Laravel 之验证器上(控制器参数解析)

前言#

今天是 2019 年 12 月 30 号了,哈哈,旧的一年即将过去,新的一年即将来临。老司机继续带你在装逼的道路上越走越远,没错,我们要做一个硬核的装逼侠。

准备工作#

由于我现在讲解的每一部分内容都不是独立存在的,与前面讲解的内容有关联,所以我希望你至少阅读过一下 2 篇博文,这是必要的:

  1. Laravel 之路由分发
  2. Laravel 之路由匹配

代码讲解#

我们直接进入到 Illuminate\Routing\Router 类的:

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

直接调用 dispatchToRoute 方法,很简单:

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

调用 runRoute 方法:

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

继续调用 runRouteWithinStack 方法:

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

上面贴出的代码,我已经在 Laravel 之路由分发Laravel 之路由匹配讲解过多次了,不熟悉的可以再次回去看一遍,不在多讲,我们直接看 $route->run(),即调用
Illuminate\Routing\Route 类的 run 方法:

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

这里我们只关心路由中指定控制器的情况,假如说我在 web.php 文件中,我定义了一个路由:

Route::get('/debug/args/{id}/{name}', 'Admin\DebugController@args');

请大家记住这个路由,后面我们会用到,注意了我在路由中指定了 2 个参数,分别是 idname,控制器是 DebugController,动作是 args。关于 DebugController 的定义如下:

class  DebugController extends Controller
{

    public function args(DebugFormRequest $request, $name, $id)
    {
        dd($name, $id);
    }

}

这里先不多说,我们回到 Illuminate\Routing\Route 类的 run 方法,继续调用 runController 方法:

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

这里的 controllerDispatcher 方法返回的是 Illuminate\Routing\ControllerDispatcher 类的对象,关于这个我也在之前的 2 篇博文中说过了,我们进入到它的 dispatch 方法:

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

上面我标出来的就是今天分析的重点,$route->parametersWithoutNulls() 返回的当前路由的参数,比如对于前面定义的路由,当我访问它时,http://localhost/debug/args/2/DennisRitche,此时我打印它的结果:

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

这个很好理解,接下来,我们继续分析 resolveClassMethodDependencies 方法,这个方法属于 Illuminate\Routing\RouteDependencyResolverTrait 这个 trait 的,因为当前的 ControllerDispatcher 使用到了这个 trait,我们来看它:

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

我们进入到 resolveClassMethodDependencies 方法中:

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

这个方法用于解析当前控制器方法的依赖性参数,1 开始检测控制器是否存在这个方法,2 才真正开始解析参数依赖,我们进入到 resolveMethodDependencies 方法中,这里有必要提醒一下,如果你对 PHP 的反射不清楚的话,请查看我之前写的这篇详解 PHP 反射的基本使用

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

这个方法本身并不复杂,但是我不得不吐槽 Laravel 自身设计上的一点坑,为啥我怎么说,当我访问 /debug/args/2/DennisRitche 这个路由的时候,控制器的打印结果如下:

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

但是,关键的问题是,这不是我想要的,因为我的控制器方法是这么定义的:

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

所以打印的结果并不是我想要的,至于原因就在这个 resolveMethodDependencies 方法中,在这个方法中,循环遍历 $reflector->getParameters() 返回的参数,每一个参数都是 ReflectionParameter 类的对象,transformDependency 用于检测当前的参数是不是一个类对象,如果是的话,就返回一个这个类的对象:

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

getClass 方法获取参数的类名,接下来的代码,就是根据它的类名创建对象,当然了首先还是判断它是否存在默认值,返回到 resolveMethodDependencies 方法中,我们看,这段代码可能有些迷惑,我仔细给大家分析:

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

首先 is_null 判断 transformDependency 返回的值是不是为空,如果不为空的话,$instanceCount 值加 1,这个值表示解析出的实例数,
$key 表示当前的参数在参数中的位置,以 DebugControllerargs 方法为例:

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

因为第一个参数 $requestDebugFormRequest 类的对象,所以此时 $key 为 0,因此 $instanceCount 的值为 1。说到这里,有必要再说下 spliceIntoParameters 方法,这个方法很简单,但是在当前的分析中,很有必要解释哈:

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

方法 array_splice 会在 $parameters$offset 位置插入一个元素,插入的值是 $value
我们看此时插入 $request 的时候,查看结果:

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

返回到 resolveMethodDependencies 接续迭代下一个参数,此时的参数为 $name,关键的问题来了,代码会进入到 elseif 语句中,在分析它的代码前,我们来看哈,$values 的值为:

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

控制台打印:

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

好了,我们进入到 elseif 语句中,! isset($values[$key - $instanceCount]) 这个判断语句是啥意思呢?前面我们说了 $instanceCount 表示解析出的实例数,而 $key 表示当前参数在控制器方法所有参数中的位置,这个地方表明 Laravel 会在所有的路由参数中依次取值,而不是按参数名进行取值,这就是我之前给大家说的 Laravel 坑,这就是为啥当我打印控制器参数的时候,$name 值为 2,$id 参数为 "DennisRitche"。如果 ! isset($values[$key - $instanceCount]) 的返回值为 false,表示路由中无法解析出这个参数值,$parameter->isDefaultValueAvailable() 检查参数是否存在默认值,所以总结下 elseif 的判断条件意思就是,当路由中不存在这个参数的时候,检查这个参数是否存在默认值,如果存在的话,$parameter->getDefaultValue() 会获取默认值并把它存储到 $parameters 的相对应位置。

好了,resolveMethodDependencies 这里关键的地方已经给大家解释了。

解析完所有的参数之后,我们回到 Illuminate\Routing\ControllerDispatcherdispatch 方法中:

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

1 检测当前的控制器是否存在 callAction 方法,如果存在就调用它,如果不存在就直接调用对应的控制器方法。

总结#

今天的这篇博文,我简要的给大家讲述了 Laravel 是如何解析控制器方法的参数的,这是下一篇给大家讲解 Laravel 验证器的基础条件,希望大家能理解,欢迎加入 qq 群:

本作品采用《CC 协议》,转载必须注明作者和本文链接
微信:okayGoHome
本帖由系统于 5年前 自动加精
Dennis_Ritchie
《L03 构架 API 服务器》
你将学到如 RESTFul 设计风格、PostMan 的使用、OAuth 流程,JWT 概念及使用 和 API 开发相关的进阶知识。
《L02 从零构建论坛系统》
以构建论坛项目 LaraBBS 为线索,展开对 Laravel 框架的全面学习。应用程序架构思路贴近 Laravel 框架的设计哲学。