本章节以前面没有分析完成的 02. HTTP Kernel Handle 解析 和 06. RouteServiceProvider 详解 做铺垫,通过讲述其最后 runController
的内部逻辑,抛砖引玉,分析 Laravel 依赖注入的原理,
因为前面我们讲了容器对象的绑定的实现 (见:10. 容器的 singleton 和 bind 的实现),所以在这里不再详述。
定义
在大型项目中,因为类的繁多,彼此需要调用到的代码的更多,如果我们在用到对象的每一个地方去 new FooClass(... $parameters )
,那么我们的项目重复代码太多,早晚会失去优雅性和可维护性。
所以依赖注入就是在用到一个对象的方法入参声明,你要什么对象,他基于什么实现。框架
(亦或独立的 容器服务
)在执行到这里的时候,发现你声明了需要那几个对象,在 容器服务
里找到这几个对象的实例化逻辑,实例化出来扔进去执行。
路由执行中的依赖注入的实现
解析反射
|
/** |
|
* Run the route action and return the response. |
|
* |
|
* @return mixed |
|
* |
|
* @throws \Symfony\Component\HttpKernel\Exception\NotFoundHttpException |
|
*/ |
|
protected function runController() |
|
{ |
|
return $this->controllerDispatcher()->dispatch( |
|
$this, $this->getController(), $this->getControllerMethod() |
|
); |
|
} |
这里的 Route
对象本身已经是单个路由了,所以 $this->getController()
和 $this->getControllerMethod()
取得的具体的 Controller 类名和方法名。
核心代码其实就是这个 $this->controllerDispatcher()->dispatch()
方法了
|
/** |
|
* Dispatch a request to a given controller and method. |
|
* |
|
* @param \Illuminate\Routing\Route $route |
|
* @param mixed $controller |
|
* @param string $method |
|
* @return mixed |
|
*/ |
|
public function dispatch(Route $route, $controller, $method) |
|
{ |
|
$parameters = $this->resolveClassMethodDependencies( |
|
$route->parametersWithoutNulls(), $controller, $method |
|
); |
|
|
|
if (method_exists($controller, 'callAction')) { |
|
return $controller->callAction($method, $parameters); |
|
} |
|
|
|
return $controller->{$method}(...array_values($parameters)); |
|
} |
第一步 解析参数
|
$parameters = $this->resolveClassMethodDependencies( |
|
$route->parametersWithoutNulls(), $controller, $method |
|
); |
resolveClassMethodDependencies
的实现
|
/** |
|
* Resolve the object method's type-hinted dependencies. |
|
* |
|
* @param array $parameters |
|
* @param object $instance |
|
* @param string $method |
|
* @return array |
|
*/ |
|
protected function resolveClassMethodDependencies(array $parameters, $instance, $method) |
|
{ |
|
if (! method_exists($instance, $method)) { |
|
return $parameters; |
|
} |
|
|
|
return $this->resolveMethodDependencies( |
|
$parameters, new ReflectionMethod($instance, $method) |
|
); |
|
} |
其中
|
if (! method_exists($instance, $method)) { |
|
return $parameters; |
|
} |
为什么没报错,是考虑到 controller 也是可以用 __call
魔术方法来绑定路由的。
resolveMethodDependencies
的逻辑就是将传入的反射对象的入参解析完成
|
/** |
|
* Resolve the given method's type-hinted dependencies. |
|
* |
|
* @param array $parameters |
|
* @param \ReflectionFunctionAbstract $reflector |
|
* @return array |
|
*/ |
|
public function resolveMethodDependencies(array $parameters, ReflectionFunctionAbstract $reflector) |
|
{ |
|
$instanceCount = 0; |
|
|
|
$values = array_values($parameters); |
|
|
|
foreach ($reflector->getParameters() as $key => $parameter) { |
|
$instance = $this->transformDependency( |
|
$parameter, $parameters |
|
); |
|
|
|
if (! is_null($instance)) { |
|
$instanceCount++; |
|
|
|
$this->spliceIntoParameters($parameters, $key, $instance); |
|
} elseif (! isset($values[$key - $instanceCount]) && |
|
$parameter->isDefaultValueAvailable()) { |
|
$this->spliceIntoParameters($parameters, $key, $parameter->getDefaultValue()); |
|
} |
|
} |
|
|
|
return $parameters; |
|
} |
容器挂靠
紧接前文代码
|
/** |
|
* Attempt to transform the given parameter into a class instance. |
|
* |
|
* @param \ReflectionParameter $parameter |
|
* @param array $parameters |
|
* @return mixed |
|
*/ |
|
protected function transformDependency(ReflectionParameter $parameter, $parameters) |
|
{ |
|
$class = $parameter->getClass(); |
|
|
|
// If the parameter has a type-hinted class, we will check to see if it is already in |
|
// the list of parameters. If it is we will just skip it as it is probably a model |
|
// binding and we do not want to mess with those; otherwise, we resolve it here. |
|
if ($class && ! $this->alreadyInParameters($class->name, $parameters)) { |
|
return $parameter->isDefaultValueAvailable() |
|
? $parameter->getDefaultValue() |
|
: $this->container->make($class->name); |
|
} |
|
} |
[划重点,前方高能]
如果此参数为一个类,且不是路由中出现的参数,返回 默认值(如有),否则从容器创建
|
: $this->container->make($class->name); |
路由的执行
dispatch
方法的下半段
|
if (method_exists($controller, 'callAction')) { |
|
return $controller->callAction($method, $parameters); |
|
} |
|
|
|
return $controller->{$method}(...array_values($parameters)); |
如果控制器定义了 callAction
,执行。
接着直接调用 controller 的方法。