Laravel 之依赖注入浅析

laravel容器包含控制反转和依赖注入,使用起来就是,先把对象bind好,需要时可以直接使用make来取就好。
具体分析参照:http://laravelacademy.org/post/769.html

通常我们的调用如下。

$config = $container->make('config');
$connection = new Connection($config);

比较好理解,这样的好处就是不用直接 new 一个实例了,方法传值没啥改变,还可以多处共享此实例。

但这跟依赖注入有什么关系,真正的依赖注入是不需给方法传递任何参数值,只需要指明方法参数类型,代码自动查找关系依赖自动注入。
这个特性在 laravel 的 Controller、Job 等处可以体现,如下:

class TestController extends Controller
{
    public function anyConsole(Request $request, Auth $input)
    {
        //todo
    }
}

我们来看下他是怎么实现自动依赖注入的:

laravel 依赖注入原理:

index.php 调用 Kernel ,经过多层 Kernel 管道调用,再到 Router ,经过多层中间件管道调用。最终定位到
Illuminate/Routing/Route.php 第124行。

public function run(Request $request)
{
    $this->container = $this->container ?: new Container;
    try {
        if (! is_string($this->action['uses'])) {
            return $this->runCallable($request);
        }

        if ($this->customDispatcherIsBound()) {
            return $this->runWithCustomDispatcher($request);
        }

        return $this->runController($request);
    } catch (HttpResponseException $e) {
        return $e->getResponse();
    }
}

判断 $this->action['uses'](格式行如:\App\Http\Controller\Datacenter\RealTimeController@anyConsole)是否字符串, $this->customDispatcherIsBound判断是否绑定了用户自定义路由。然后跳转到 $this->runController($request)

protected function runController(Request $request)
{
    list($class, $method) = explode('@', $this->action['uses']);

    $parameters = $this->resolveClassMethodDependencies(
        $this->parametersWithoutNulls(), $class, $method
    );

    if (! method_exists($instance = $this->container->make($class), $method)) {
        throw new NotFoundHttpException;
    }

    return call_user_func_array([$instance, $method], $parameters);
}

$this->resolveClassMethodDependencies 这个方法一看名字就知道是我们要找的方法。$this->parametersWithoutNulls()是过滤空字符,$class$method分别行如:\App\Http\Controller\Datacenter\RealTimeControlleranyConsole

protected function resolveClassMethodDependencies(array $parameters, $instance, $method)
{
    if (! method_exists($instance, $method)) {
        return $parameters;
    }

    return $this->resolveMethodDependencies(
        $parameters, new ReflectionMethod($instance, $method)
    );
}

new ReflectionMethod($instance, $method) 是拿到类方法的反射对象,参见文档:http://www.php.net/manual/zh/class.reflect...

下面跳转到Illuminate/Routing/RouteDependencyResolverTrait.php 第54行。

public function resolveMethodDependencies(array $parameters, ReflectionFunctionAbstract $reflector)
{
    $originalParameters = $parameters;

    foreach ($reflector->getParameters() as $key => $parameter) {
        $instance = $this->transformDependency(
            $parameter, $parameters, $originalParameters
        );

        if (! is_null($instance)) {
            $this->spliceIntoParameters($parameters, $key, $instance);
        }
    }

    return $parameters;
}

通过反射类方法得到类参数数组,然后遍历传递给 $this->transformDependency 方法。如果实例获取不到则调用 $this->spliceIntoParameters 清楚该参数。

protected function transformDependency(ReflectionParameter $parameter, $parameters, $originalParameters)
{
    $class = $parameter->getClass();
    if ($class && ! $this->alreadyInParameters($class->name, $parameters)) {
        return $this->container->make($class->name);
    }
}

终于看到了容器的影子,没错最终对象还是通过容器的 make 方法取出来的。至此参数就构造好了,然后最终会被 runController 方法的 call_user_func_array 回调。

总结:

  1. 依赖注入原理其实就是利用类方法反射,取得参数类型,然后利用容器构造好实例。然后再使用回调函数调起。
  2. 注入对象构造函数不能有参数。否则会报错。Missing argument 1
  3. 依赖注入故然好,但它必须要由 Router 类调起,否则直接用 new方式是无法实现注入的。所以这就为什么只有 Controller 、Job 类才能用这个特性了。
本作品采用《CC 协议》,转载必须注明作者和本文链接
本帖由系统于 5年前 自动加精
《L01 基础入门》
我们将带你从零开发一个项目并部署到线上,本课程教授 Web 开发中专业、实用的技能,如 Git 工作流、Laravel Mix 前端工作流等。
《G01 Go 实战入门》
从零开始带你一步步开发一个 Go 博客项目,让你在最短的时间内学会使用 Go 进行编码。项目结构很大程度上参考了 Laravel。
讨论数量: 5
Corwien

写得非常详细,多谢分享!

6年前 评论

我有个问题想问下,那一般依赖注入是不是都是一接口绑定一个实例,那要是一个接口,多种实现,这样,怎么互相绑定呢

5年前 评论

runController 在那里调用了request这里没有说明,如果要将request作为参数应怎么处理?

4年前 评论

不是只有Controller、Job类里可用,也可自主调用make: \App::make('Namespace\DemoClass');

3年前 评论
evanwoo 1年前

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!