Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

09. 容器的依赖注入机制 #9

Open
xiaohuilam opened this issue Sep 27, 2018 · 0 comments
Open

09. 容器的依赖注入机制 #9

xiaohuilam opened this issue Sep 27, 2018 · 0 comments
Labels
book The digital book for laravel learning

Comments

@xiaohuilam
Copy link
Owner

xiaohuilam commented Sep 27, 2018

本章节以前面没有分析完成的 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 的方法。

@xiaohuilam xiaohuilam changed the title 09. 容器的依赖注入机制 [TODO] 09. 容器的依赖注入机制 [0%] Sep 27, 2018
@xiaohuilam xiaohuilam changed the title 09. 容器的依赖注入机制 [0%] 09. 容器的依赖注入机制 Sep 27, 2018
@xiaohuilam xiaohuilam added the book The digital book for laravel learning label Sep 27, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
book The digital book for laravel learning
Projects
None yet
Development

No branches or pull requests

1 participant