老死机带你深入理解 Laravel 之验证器上(控制器参数解析)
前言
今天是2019年12月30号了,哈哈,旧的一年即将过去,新的一年即将来临。老司机继续带你在装逼的道路上越走越远,没错,我们要做一个硬核的装逼侠。
准备工作
由于我现在讲解的每一部分内容都不是独立存在的,与前面讲解的内容有关联,所以我希望你至少阅读过一下2篇博文,这是必要的:
代码讲解
我们直接进入到Illuminate\Routing\Router
类的:
直接调用dispatchToRoute
方法,很简单:
调用runRoute
方法:
继续调用runRouteWithinStack
方法:
上面贴出的代码,我已经在Laravel 之路由分发和Laravel 之路由匹配讲解过多次了,不熟悉的可以再次回去看一遍,不在多讲,我们直接看$route->run()
,即调用Illuminate\Routing\Route
类的run
方法:
这里我们只关心路由中指定控制器的情况,假如说我在web.php
文件中,我定义了一个路由:
Route::get('/debug/args/{id}/{name}', 'Admin\DebugController@args');
请大家记住这个路由,后面我们会用到,注意了我在路由中指定了2个参数,分别是id
和name
,控制器是DebugController
,动作是args
。关于DebugController
的定义如下:
class DebugController extends Controller
{
public function args(DebugFormRequest $request, $name, $id)
{
dd($name, $id);
}
}
这里先不多说,我们回到Illuminate\Routing\Route
类的run
方法,继续调用runController
方法:
这里的controllerDispatcher
方法返回的是Illuminate\Routing\ControllerDispatcher
类的对象,关于这个我也在之前的2篇博文中说过了,我们进入到它的dispatch
方法:
上面我标出来的就是今天分析的重点,$route->parametersWithoutNulls()
返回的当前路由的参数,比如对于前面定义的路由,当我访问它时,http://localhost/debug/args/2/DennisRitche
,此时我打印它的结果:
这个很好理解,接下来,我们继续分析resolveClassMethodDependencies
方法,这个方法属于Illuminate\Routing\RouteDependencyResolverTrait
这个trait的,因为当前的ControllerDispatcher使用到了这个trait,我们来看它:
我们进入到resolveClassMethodDependencies
方法中:
这个方法用于解析当前控制器方法的依赖性参数,1开始检测控制器是否存在这个方法,2才真正开始解析参数依赖,我们进入到resolveMethodDependencies
方法中,这里有必要提醒一下,如果你对PHP的反射不清楚的话,请查看我之前写的这篇详解 PHP 反射的基本使用:
这个方法本身并不复杂,但是我不得不吐槽Laravel自身设计上的一点坑,为啥我怎么说,当我访问/debug/args/2/DennisRitche
这个路由的时候,控制器的打印结果如下:
但是,关键的问题是,这不是我想要的,因为我的控制器方法是这么定义的:
所以打印的结果并不是我想要的,至于原因就在这个resolveMethodDependencies
方法中,在这个方法中,循环遍历$reflector->getParameters()
返回的参数,每一个参数都是ReflectionParameter
类的对象,transformDependency
用于检测当前的参数是不是一个类对象,如果是的话,就返回一个这个类的对象:
getClass
方法获取参数的类名,接下来的代码,就是根据它的类名创建对象,当然了首先还是判断它是否存在默认值,返回到resolveMethodDependencies
方法中,我们看,这段代码可能有些迷惑,我仔细给大家分析:
首先is_null
判断transformDependency
返回的值是不是为空,如果不为空的话,$instanceCount
值加1,这个值表示解析出的实例数,$key
表示当前的参数在参数中的位置,以DebugController
的args
方法为例:
因为第一个参数$request
是DebugFormRequest
类的对象,所以此时$key
为0,因此$instanceCount
的值为1。说到这里,有必要再说下spliceIntoParameters
方法,这个方法很简单,但是在当前的分析中,很有必要解释哈:
方法array_splice
会在$parameters
的$offset
位置插入一个元素,插入的值是$value
。
我们看此时插入$request
的时候,查看结果:
返回到resolveMethodDependencies
接续迭代下一个参数,此时的参数为$name
,关键的问题来了,代码会进入到elseif语句中,在分析它的代码前,我们来看哈,$values
的值为:
控制台打印:
好了,我们进入到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\ControllerDispatcher
的dispatch
方法中:
1检测当前的控制器是否存在callAction
方法,如果存在就调用它,如果不存在就直接调用对应的控制器方法。
总结
今天的这篇博文,我简要的给大家讲述了Laravel是如何解析控制器方法的参数的,这是下一篇给大家讲解Laravel验证器的基础条件,希望大家能理解,欢迎加入qq群:
本作品采用《CC 协议》,转载必须注明作者和本文链接
推荐文章: