xiaohuilam/laravel

09. 容器的依赖注入机制

Opened this issue · 0 comments

本章节以前面没有分析完成的 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);
}
}

[划重点,前方高能]
如果此参数为一个类,且不是路由中出现的参数,返回 默认值(如有),否则从容器创建

路由的执行

dispatch 方法的下半段

if (method_exists($controller, 'callAction')) {
return $controller->callAction($method, $parameters);
}
return $controller->{$method}(...array_values($parameters));

如果控制器定义了 callAction,执行。
接着直接调用 controller 的方法。