Container make 解析机制

resolve 解析是容器中最复杂的部分,有很多小细节只有通读整个流程反复试验后才能领略作者的设计的目的。

Make 和 Resolve 都是从容器中解析实例 (这个实例是指 concrete) 出来。简单说就是从容器中把前面 bind 进去的东西拿出来用。

这里需要明确的是,make 解析的时候会调用 build 函数实例化对象,就是说理论上如果绑定的是一个字符串,laravel
默认这是一个可以实例化对象的类路径。
那我们如果想要绑定一个纯粹的字符串或者数字,我们可以使用闭包函数。让闭包返回我们需要的类型。具体看下面的源码

把 resolve 和 make 放在一起是因为其实上在 Container 类中,make 就是 resolve 的一个包装。
我们看看 make 方法:很简单直接调用了 resolve 方法,类似的还有 makeWith 方法,有兴趣的可以看看。

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

先整体看下 resolve 函数源码:

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

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

        // If an instance of the type is currently being managed as a singleton we'll
        // just return an existing instance instead of instantiating new instances
        // so the developer can keep using the same objects instance every time.
        if (isset($this->instances[$abstract]) && ! $needsContextualBuild) {
            return $this->instances[$abstract];
        }

        $this->with[] = $parameters;

        $concrete = $this->getConcrete($abstract);

        // We're ready to instantiate an instance of the concrete type registered for
        // the binding. This will instantiate the types, as well as resolve any of
        // its "nested" dependencies recursively until all have gotten resolved.
        if ($this->isBuildable($concrete, $abstract)) {
            $object = $this->build($concrete);
        } else {
            $object = $this->make($concrete);
        }

        // If we defined any extenders for this type, we'll need to spin through them
        // and apply them to the object being built. This allows for the extension
        // of services, such as changing configuration or decorating the object.
        foreach ($this->getExtenders($abstract) as $extender) {
            $object = $extender($object, $this);
        }

        // If the requested type is registered as a singleton we'll want to cache off
        // the instances in "memory" so we can return it later without creating an
        // entirely new instance of an object on each subsequent request for it.
        if ($this->isShared($abstract) && ! $needsContextualBuild) {
            $this->instances[$abstract] = $object;
        }

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

        // Before returning, we will also set the resolved flag to "true" and pop off
        // the parameter overrides for this build. After those two things are done
        // we will be ready to return back the fully constructed class instance.
        $this->resolved[$abstract] = true;

        array_pop($this->with);

        return $object;
    }

还是从参数说起

0.1. 参数 $abstruct, 获取在容器中的服务的名字,或者叫 id // 不多说都知道
0.2. 参数 $parameters, 有些实例对象实例化的时候会需要参数,这个 $parametters 就是我们传入的参数。

举例:看代码,上一章我们知道,bind 只是绑定一个闭包,啥也不干,所以不用传入参数,因为压根没有实例化对象。但是当我们这里要 make 解析的时候,即实例化 Boss.class 的时候,我们要把这个 Object 类型的对象传进去。Boss.class 才能实例化。

app()->bind('Boss', Boss.class);

class Boss(){
    private $obj;
    //这里构造函数需要一个对象才能实例化。
    public function __construct(Object $obj){
        $this->obj = $obj;
    }
}

app()->make('Boss'[new Object()]);
  1. 获取 $abstract 的别名。请参看・别名・那章。
$abstract = $this->getAlias($abstract);
  1. 设置一个变量 $needsContextualBuild 来做标记,标记当前这个解析的实例需不需要上下文绑定。在上下文绑定那章我们也说了,上下文绑定其实就是依赖绑定,就是判断当前的 make 的实例需不需要依赖。满足下面两个条件中的任意一个就需要:
    a. 传入的参数不为空。很好理解,你都传入参数了,这个参数上面刚刚讲了就是为了当前实例化的时候传入作为依赖的。
    b. 通过函数 getContextualConcrete,获取到了当前解析的这个类,是否已经有了上下文绑定的依赖。(就是事先已经使用上下文绑定过了),这个其实虚 的没有任何作用,往下细看
$needsContextualBuild = ! empty($parameters) || ! is_null(
            $this->getContextualConcrete($abstract)
        );

2.1. 那让我们看看 getContextualConcrete($abstract) 方法如何获取事先绑定的上下文依赖的:

2.1.1. 首先判断是否在上下文绑定的数组中存在 abstruct 的实例 concrete,如果有就返回。// 直接从数组中找。
2.1.2. 如果没有,看看这个 $abstractAliases 数组里面有没有 $abstruct 别名,这个数组前面” 别名” 章节我们提过,和 $aliases 数据保存相反格式,保存 abstruct 和 alias 关系的数组。注意,后面的数组 value 值才是别名,键值‘app’是 abstruct
格式如下:

$abstractAliases = [
  app = {array} [3]
  0 = "Illuminate\Foundation\Application"
  1 = "Illuminate\Contracts\Container\Container"
  2 = "Illuminate\Contracts\Foundation\Application"
  blade.compiler = {array} [1]
  0 = "Illuminate\View\Compilers\BladeCompiler"
  ...
]

继续看源代码。如果这个数组是空的,直接返回了。

2.1.3. 如果这个数组不是空的,遍历所有 abstruct 的别名,这个别名在 binding 数组中是否存在。

简单说就是 abstruct 如果不在上下文绑定的数组中,那么看看 abstruct 的别名是否在上下文绑定数组中。最后判断一下返回。

getContextualConcrete 代码入下:

protected function getContextualConcrete($abstract)
{
    if (! is_null($binding = $this->findInContextualBindings($abstract))) {
        return $binding;
    }

    if (empty($this->abstractAliases[$abstract])) {
        return;
    }

    foreach ($this->abstractAliases[$abstract] as $alias) {
        if (! is_null($binding = $this->findInContextualBindings($alias))) {
            return $binding;
        }
    }
}

2.1.3.1 重点来了,我们去看看 findInContextualBindings 源码:

protected function findInContextualBindings($abstract)
{
    if (isset($this->contextual[end($this->buildStack)][$abstract])) {
        return $this->contextual[end($this->buildStack)][$abstract];
    }
}

还记得上下文绑定那章的存储结构就是这样:contextual[when][give] = implement。这里就是取对应的值。
但是我们发现他在取 [give] 值的时候它使用了 end ($this->buildStack) buildStack 是 build 的实例的堆栈,我们上下文绑定的流程中完全没有这个绑定。也就是说我们从 resolve 进来你是找不到这个值的,这完全是虚的没有任何作用,getContextualConcrete 不会取得任何值。他的存在其实是给 build 函数创建依赖对象的时候,会递归再次回来 make 解析依赖类用的。看下一章 build 方法解析

总结第二点,其实我们这里主要判断是就是有没有 parameters,getContextualConcrete 似乎完全不会取得任何值。

  1. 回到主线 resolve 函数,如果在数组 instances 中已经存才这个 abstruct 的对象了并且不需要上下文绑定,直接调用这个 instances 中的值返回。我们前面章节知道 instances 数组是保存可以 shared 的实体对象。既然有了,并且没有依赖,就直接返回。
    这里有个问题,如果有依赖,instances 中的值为什么不能直接返回,因为依赖可能会变化,仔细想想是不是。你前面使用 instance 传入的有依赖的对象的参数,和这次我们要求的对象传入的依赖参数,可能是不同的。比如 以前存储的 new A(‘1’), 这次需要的 new A(‘2’),一个对象参数不同。
if (isset($this->instances[$abstract]) && ! $needsContextualBuild) 
{
    return $this->instances[$abstract];
}

这里有一个问题,通过 instance() 方法是可以保存任何类型数据的。但是如果 instances 数组中没有事先存在的值,那么 make 解析的字符串默认被当做一个类路径的。(后面章节有 instace 绑定源码分析)举例如下:

//使用instance存入字符串绑定。成功
$this->app->instance('money',"11");
$re = $this->app->make('money');//success
//通过闭包绑定字符串类型的值 成功
$this->app->bind('money', function(){return "11";});
$re = $this->app->make('money');//success
//直接绑定字符串,同时instances数组中不存在任何值,11被当做一个类路径处理。失败
$this->app->bind('money', '11');
$re = $this->app->make('money');//fail

4.1 前面的条件没成立的话,接下来,把参数 parameters 存入 with 数组,前面讲过了,parameters 是实例化的时候需要的依赖,所以暂存于 with 数组。

$this->with[] = $parameters;

4.2. 接来下通过函数 getConcrete ($abstract) 获取 concrete

$concrete = $this->getConcrete($abstract);

我们看 getConcrete 源代码:

protected function getConcrete($abstract)
{
    if (! is_null($concrete = $this->getContextualConcrete($abstract))) {
        return $concrete;
    }

    if (isset($this->bindings[$abstract])) {
        return $this->bindings[$abstract]['concrete'];
    }

    return $abstract;
}

主要的思路是:
a. 看看上下文绑定数组中有没有 $abstruct 对应的 concrete 值,如果有,太好了,最复杂的情况就是上下文绑定。直接返回就好了。连依赖都已经添加了。(参看山下文绑定存储结构和使用方法)
这里要特别注意,上下文绑定获取的 concrete 值可以是一个类路径,也可以是一个闭包(看看文档如何使用上下文绑定就知道了,可以传入类路径也可以是闭包。)。但是在后面的处理对两个情况是不一样的

和上面情况雷同,其实这里 getConcrete 还是调用了 getContextualConcrete,但 buildstack
中没有值,所以这个是虚的。暂时是没有值的。build 解析依赖类的时候递归回来才有这个 buildstack 值

b. 如果没找到上下文绑定,就是一个普通绑定,就去 bindings 的数组中看看有没有 $abstruct 对应的 concrete 值,从而确认是不是以前有绑定过。同样的 $concrete 可以是一个闭包,也可以是一个类路径。
c. 都没有,说明没有绑定!!直接返回 $abstruct。

这里说明什么呢,我猜想我们是可以不用绑定 bind 函数,而直接 make 的,这样的话可以直接把 $abstruct 当做 $concrete 来解析.

//实测有效,直接返回Money::class 对象。
$boss= app()->make(Money::class); 

这个方法处理的结果也有三种可能:
a. 上下文绑定的 concrete (这个其实没有)
b.binding 数组中的 concrete
c. 把 $concrete === $abstruct 相等。
这里的 c 步骤到底做了什么,怎么处理的?我们往下看代码。第五步。

  1. 获取解析的对象了。
if ($this->isBuildable($concrete, $abstract)) {
    $object = $this->build($concrete);
} else {
    $object = $this->make($concrete);
}

5.1 首先,我们要看下函数 isBuildable 函数是什么要求。
如果 $concrete === $abstract 或者 concrete 是一个闭包,好办返回 true。

protected function isBuildable($concrete, $abstract)
{
    return $concrete === $abstract || $concrete instanceof Closure;
}

5.2 如果是 true,那么使用 build 函数处理这个 object
我们在这里简单说下 build 具体的会在下一章 build 源码中分析。build 的作用是这样的:
a. 如果 concrete 是闭包,build 执行闭包函数。
b. 不是闭包,build 函数会使用反射产生当前 $concrete 类的对象。和前面我们的猜想一样。既然 a b s t r u c t = = = abstruct=== abstruct===concrete,那么直接解析,都不用绑定。

5.3 如果 isBuildable 返回的是 false 呢?就是 $concrete 的值是・类路径・的情况,调用 make 进入递归。如下 give 给的不是一个闭包是一个类路径。则进入 make。

$container
    ->when(VideoController::class)
    ->needs(Filesystem::class)
    ->give(S3Filesystem::class);

make 再去 getConcrete 函数,去上下文绑定数组和 binding 数组,查询这个时候这个・类路径下・(就是 abstruct)有没有对应的闭包或类路径。但不管怎么样。最后下来要么闭包,要么相等,他都会进入 build 函数创建对象。

  1. 到此,我们得到了解析出来的 object 对象。
    然后第六步我们要看看是否有扩展绑定 entend 的处理,参看 0.2 章节,执行
foreach ($this->getExtenders($abstract) as $extender) {
    $object = $extender($object, $this);
}
  1. 是否是单例分享的,如果是的话就存入 instance,参看 0.4 章节
if ($this->isShared($abstract) && ! $needsContextualBuild) {
    $this->instances[$abstract] = $object;
}
  1. 接着触发各个回调函数,参看 0.3 章节,执行回调,这个函数就是触发 3 个地方的回调函数。
$this->fireResolvingCallbacks($abstract, $object);
  1. 标记已经解析了。并且把参数从 with 中 pop 掉,没用了。这个 with 在 build 方法中使用了,在 make 方法中没有用到。
$this->resolved[$abstract] = true;
array_pop($this->with);

最后返回对象。

总结:
make(解析)相对复杂。但是主要关注几个大步骤就能明白流程。

首先获取最终的别名。
设置是否是・上下文绑定・的标记
如果在 shared 的 instances 数组中找到了,同时又不是有上下文绑定需求的。直接返回对象。结束程序。
否则,把实例化对象所依赖的参数 parameters 暂存 with 数组
1 通过 getConcrete 方法获取 $concrete. 注意这里的 concrete 还不是对象,是类路径或者是一个闭包函数
有了 $concrete,如果是闭包,我们利用 build 函数生成对象。
1 如果是类路径,我们要再递归,看看这个路径下是否还有 $concrete 的绑定。如果有再递归,像别名一样,找到真正那个。如果没有,使用 build 函数反射原理生成对象返回,with 数组将在 build 反射中使用。
完成对象生成,看看有没有 extend 扩展
看看是否需要 shard,把对象存入 instance 中
触发各个回调函数
记录这个 abstruct 已经解析过了。
1 把 with 数组中 parameters 清空掉。
返回对象

————————————————
原文作者:HarveyNorman
转自链接:https://learnku.com/articles/41762
版权声明:著作权归作者所有。商业转载请联系作者获得授权,非商业转载请保留以上作者信息和原文链接。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值