Laravel 服务容器之 make

未匹配的标注

简介

本篇主要讲解 Laravel 服务容器中 make 方法主要作用

正文

下面是 public/index.php 入口文件的一段代码,相信大家都见过

$kernel = $app->make(Illuminate\Contracts\Http\Kernel::class);

它作用解析出在服务容器 bindings 属性中以 'Illuminate\Contracts\Http\Kernel' 为键,对应的服务对象。据上一章我们知道是 'App\Http\Kernel' 的类对象

注意:以 'Illuminate\Contracts\Http\Kernel' 为键,实际对应不是 'App\Http\Kernel' 的类对象,而是对应能够生成 'App\Http\Kernel' 类对象的闭包函数。

那么他是如何解析出的呢,我们看一下源码

public function make($abstract, array $parameters = [])
{
    return $this->resolve($abstract, $parameters);
}

实际调用了 resolve 方法,我们看一下 resolve 方法。

看下面这方法,是不是头大,这么多,其实核心的就那么几个地方,我已标注

protected function resolve($abstract, $parameters = [])
{
    $abstract = $this->getAlias($abstract);

    $needsContextualBuild = ! empty($parameters) || ! is_null(
        $this->getContextualConcrete($abstract)
    );

    // ★核心一★:检测之前是否解析过对应的对象,有则返回对应对象;一种缓存机制,防止重复解析,提高程序运行效率
    if (isset($this->instances[$abstract]) && ! $needsContextualBuild) {
        return $this->instances[$abstract];
    }

    $this->with[] = $parameters;

    // ★核心二★:通过 $abstract 到 bindings 属性中取得生成对象的闭包函数,没有则返回 $abstract 本身
    $concrete = $this->getConcrete($abstract);

    // ★核心三★:判断是否可以创建对象,来确定调用 build 方法还是重新调用 make 方法
    if ($this->isBuildable($concrete, $abstract)) {
        $object = $this->build($concrete);
    } else {
        $object = $this->make($concrete);
    }

    foreach ($this->getExtenders($abstract) as $extender) {
        $object = $extender($object, $this);
    }

    if ($this->isShared($abstract) && ! $needsContextualBuild) {
        $this->instances[$abstract] = $object;
    }

    $this->fireResolvingCallbacks($abstract, $object);

    $this->resolved[$abstract] = true;

    array_pop($this->with);

    return $object;
}

由于第一次执行,核心一忽略。当以 'Illuminate\Contracts\Http\Kernel' 为 $abstract 时,获取的闭包函数定义的位置如下

protected function getClosure($abstract, $concrete)
{
    return function ($container, $parameters = []) use ($abstract, $concrete) {
        if ($abstract == $concrete) {
            return $container->build($concrete);
        }

        return $container->make($concrete, $parameters);
    };
}

会执行上面代码 function 里面的内容,因为 Illuminate\Contracts\Http\Kernel 不等于 App\Http\Kernel ,故以 App\Http\Kernel 为 $abstract 重新调用了 make 方法

此时的 make 方法可以执行到 build 方法,我们来看一下

public function build($concrete)
{
    // 首先判断 concrete 是不是闭包,如果是就会执行
    if ($concrete instanceof Closure) {
        return $concrete($this, $this->getLastParameterOverride());
    }

    // PHP 反射类操作
    $reflector = new ReflectionClass($concrete);

    if (! $reflector->isInstantiable()) {
        return $this->notInstantiable($concrete);
    }

    $this->buildStack[] = $concrete;

    // 获取 concrete 类的构造方法
    $constructor = $reflector->getConstructor();

    if (is_null($constructor)) {
        array_pop($this->buildStack);

        return new $concrete;
    }

    // 获取 concrete 类的构造方法上的参数
    $dependencies = $constructor->getParameters();

    // 根据参数的类型约束,注入依赖
    $instances = $this->resolveDependencies(
        $dependencies
    );

    array_pop($this->buildStack);

    return $reflector->newInstanceArgs($instances);
}

此时的 $concrete 等于 'App\Http\Kernel',会调用 PHP 内置的反射类,对 App\Http\Kernel 进行处理。

关于 ReflectionClass

官方文档

ReflectionClass:我的理解是,这是一个对类进行处理的对象,它甚至能够获取类中的注释,执行类中的私有方法;getConstructor 返回一个 ReflectionMethod 对象,它可以对类中方法进行更进一步处理和控制,比如获取方法调用时的参数列表,包含参数的类型约束,这点很重要,因为它是 Laravel 实现依赖注入的手段;反射方法对象中的 getParameters 将返回一个 ReflectionParameter 对象,它里面就包含了类型约束和参数名称。

$instances = $this->resolveDependencies(
    $dependencies
);

看到上面这段代码了吗,resolveDependencies 方法,就是解析出要类构造方法中的约束类,通过多次 make 自动获取所依赖的对象,然后通过反射类的 newInstanceArgs 实例化

最终获取了 App\Http\Kernel 类的对象,并且仅仅通过一行字符串,其中构造函数所依赖的对象,全部由 make 自动处理,用户可以不再管理那个依赖这个依赖了,不仅变得方便、变得解耦,还变得功能健全

本篇如有错误、不当或者需补充的内容,请各位同僚多提宝贵意见。

本文章首发在 LearnKu.com 网站上。

上一篇 下一篇
《L02 从零构建论坛系统》
以构建论坛项目 LaraBBS 为线索,展开对 Laravel 框架的全面学习。应用程序架构思路贴近 Laravel 框架的设计哲学。
《G01 Go 实战入门》
从零开始带你一步步开发一个 Go 博客项目,让你在最短的时间内学会使用 Go 进行编码。项目结构很大程度上参考了 Laravel。
讨论数量: 0
发起讨论 查看所有版本


暂无话题~