build 解析机制

build 的作用就是生成对象,具体他是怎么根据不同情况生成对象的,看源代码。

源代码:

//0 -> 参数
public function build($concrete)

{
    //1 
    if ($concrete instanceof Closure) {

        return $concrete($this, $this->getLastParameterOverride());

    }

    //2
    $reflector = new ReflectionClass($concrete);

    if (! $reflector->isInstantiable()) {

        return $this->notInstantiable($concrete);

    }

    //3
    $this->buildStack[] = $concrete;

    $constructor = $reflector->getConstructor();

    if (is_null($constructor)) {

        array_pop($this->buildStack);

        return new $concrete;

    }
    //4
    $dependencies = $constructor->getParameters();

    $instances = $this->resolveDependencies(

        $dependencies

    );

    array_pop($this->buildStack);

    return $reflector->newInstanceArgs($instances);

}
  1. 还是从参数说起,传入的实例 concrete, 可以是闭包函数,可以是一个类路径。还是重新提一下,concrete 并不是实体类对象,而是可以产生对象的类或者闭包。和实体对象集合 instance 有区别。

  2. 如果这个 concrete 是一个闭包函数,我们就直接执行这个闭包。

if ($concrete instanceof Closure) {
    return $concrete($this, $this->getLastParameterOverride());
}

这里的闭包的形式为:
$concrete($this, $this>getLastParameterOverride());

有两个参数,但是不传参数也是可以的。

1.1 第二个参数 $this->getLastParameterOverride(), 我们看下源代码:make 解析那章,解析的时候把 parameters 存入了 with 数组,这个时候就是使用 getLastParameterOverride 获取 with
数组的最后一个值,也就是我们 make 时候刚刚放入的对象的 parameters。

protected function getLastParameterOverride()
{
    return count($this->with) ? end($this->with) : [];
}
  1. 创建一个 php 的 ReflectionClass 对象,包含类的信息(参考 php 文档),ReflectionClass 针对类,ReflectionObject 则是对对象。

所以这里 concrete 是一个类路径。正如前面提到的。

$reflector = new ReflectionClass($concrete);

2.1 如果这不是一个可以初始化的类,那么 notInstantiable 方法会抛出一个异常。

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

notInstantiable 方法源代码:发现他无非就是组织一个 message,然后抛出了一个异常。

他返回的异常提示是 Targe … is not instantiable.

protected function notInstantiable($concrete)
{

    if (! empty($this->buildStack)) {

        $previous = implode(', ', $this->buildStack);

        $message = "Target [$concrete] is not instantiable while building [$previous].";

    } else {

        $message = "Target [$concrete] is not instantiable.";

    }

    throw new BindingResolutionException($message);
}
  1. 这里通过 php 反射机制,用 concrete 类产生对象。如何实现呢?如下步骤

3.1 首先把 concrete 存入数组 buildStack 中,等下用。为什么一定要先存入一个数组呢?下面会讲。如果细看上一章,也许你知道。

$this->buildStack[] = $concrete;

3.2 然后利用前面得到的反射对象通过 getConstructor 方法得到他的构造函数,

如果构造函数是空的,就是默认没有参数的构造函数,从 buildStack 取出 concrete 使用 new 创建这个对象返回。

$constructor = $reflector->getConstructor();

if (is_null($constructor)) {

    array_pop($this->buildStack);

    return new $concrete;

}
  1. 否则就是类里有自定义构造函数,或者说可能构造函数存在依赖的情况。我们要先获取依赖再去创建对象。

4.1 通过构造函数我们获取参数依赖。就是构造函数中的参数,如果没有,返回一个空的数组。

$dependencies = $constructor->getParameters();

在进行下面的分析前,先明确 getParameters () 会返回的数组的形式,帮助后面的分析,下面为一个例子:

class dollar{

    public function __construct($import){}

        public function getAmount(){

        return 100;

    }
}

$reflect = new ReflectionClass('dollar');

$constructor = $reflect->getConstructor();

var_dump($constructor->getParameters());

最后他输出的结构是:

array(1) {
    [0] => object(ReflectionParameter)#3 (1) {
        ["name"]=>string(6) "import"
    }
}

4.2 有了上面的例子。使用 resolveDependencies 方法解析然后获取这个依赖。(不想看具体实现可以跳过到 4.3)

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

看下整个源码,然后再一步一步看。

protected function resolveDependencies(array $dependencies)

{

    $results = [];

    foreach ($dependencies as $dependency) {

        if ($this->hasParameterOverride($dependency)) {

            $results[] = $this->getParameterOverride($dependency);

            continue;

        }

        $results[] = is_null($dependency->getClass()) ? 
        $this->resolvePrimitive($dependency) : 
        $this->resolveClass($dependency);
    }

    return $results;

}

4.2.1 首先遍历这个依赖,然后使用方法 t h i s − > h a s P a r a m e t e r O v e r r i d e ( this->hasParameterOverride( this>hasParameterOverride(dependency) 判断是否参数有覆盖。什么意思呢,看 hasParameterOverride 源码,如下:

protected function hasParameterOverride($dependency)
{
    return array_key_exists(

        $dependency->name, $this->getLastParameterOverride()

    );
}

t h i s − > g e t L a s t P a r a m e t e r O v e r r i d e ( ) 上 面 提 到 过 , 获 取 m a k e 时 候 存 入 w i t h 数 组 的 参 数 。 this->getLastParameterOverride() 上面提到过,获取 make 时候存入 with 数组的参数。 this>getLastParameterOverride()makewithdependency->name 是什么,就是构造函数中参数的名字,上面的例子中,就是‘import’。简单说就是我们在 make 解析的时候是不是传递了这个依赖的值(就是 make 方法的第二个参数 parameters)。

4.2.2 继续,如果有这个覆盖,使用 getParameterOverride 获取这个依赖的值,存入 result 数组中。

$results[] = $this->getParameterOverride($dependency);

严谨的再看下 getParameterOverride 怎么实现的:

protected function getParameterOverride($dependency)

{

    return $this->getLastParameterOverride()[$dependency->name];

}

很简单还是通过 getLastParameterOverride 获取 with 数组,然后在数组中通过 name(这里就是 import)获取

4.2.3 然后 continue 循环下一个依赖。

4.2.4 如果在 with 数组中找不到对应的依赖。也就是这个依赖不是我们 make 时候设定的,故意要传入的 parameters,可能是其他的类。那么我们先判断这个依赖类是不是存在于当前代码中。

I. 如果不存在,则使用 resolvePrimitive 方法,看看上下文绑定中有没有对应的值,再看看依赖自己有没有默认值(具体看下面4.2.5 resolvePrimitive方法源码分析)。

II. 如果存在使用 resolveClass 方法,就是使用 make 函数解析这个依赖(具体看下面4.2.6 resolveClass方法源码)。

$results[] = is_null($dependency->getClass())

? $this->resolvePrimitive($dependency)

: $this->resolveClass($dependency);

4.2.5 resolvePrimitive 方法源码:

protected function resolvePrimitive(ReflectionParameter $parameter)

{

    if (! is_null($concrete = $this->getContextualConcrete('$'.$parameter->name))) {

        return $concrete instanceof Closure ? $concrete($this) : $concrete;

}

    if ($parameter->isDefaultValueAvailable()) {

        return $parameter->getDefaultValue();

    }

    $this->unresolvablePrimitive($parameter);

}

a. 首先判断这个依赖的 name 是不是在上下文绑定中 (又来了)。

b. 如果这个依赖在上下文绑定中,再次提醒,我们记得上下文绑定有两个类型,一个是闭包,一个是类路径。这里如果是闭包直接执行,如果是类路径,返回类路径。

c. 否则是否存在依赖有默认值,有默认值直接返回默认值。什么意思呢,这里的例子,如果是 $import=“100” 是这个情况。

d. 都没有那说明没办法解析,执行 unresolvablePrimitive 方法,直接排除一个无法解析的异常。源码:

protected function unresolvablePrimitive(ReflectionParameter $parameter)

{

    $message = "Unresolvable dependency resolving [$parameter] in class {$parameter->getDeclaringClass()->getName()}";

    throw new BindingResolutionException($message);

}

4.2.6 resolveClass 方法源码:

protected function resolveClass(ReflectionParameter $parameter)

{

    try {

        return $this->make($parameter->getClass()->name);

    }

    catch (BindingResolutionException $e) {

        if ($parameter->isOptional()) {

            return $parameter->getDefaultValue();

        }

        throw $e;

    }

}

a. 很明显,既然在代码中存在这个类,使用 make 函数解析这个类。(又进入解析了,又会调用 build。)这个时候才是 buildstack 有值的时候,就是说在 resolve 函数中,getContextualConcrete 才起到了真正的作用,会检查上下文绑定。回看上一章

b. 有一个情况我们要考虑,如果这个类不能被解析,就是解析失败了,(比如:这个类是抽象类)。怎么办,我们上面提过,他会抛出一个 BindingResolutionException 异常吧,这里捕获这个异常,判断这个依赖 i m p o r t 是 不 是 一 个 o p t i o n a l 的 参 数 , 什 么 意 思 , 还 是 这 个 情 况 ( import 是不是一个 optional 的参数,什么意思,还是这个情况 ( importoptional(import == 100) 有没有默认值,如果有则取他的默认值 (100) 返回,不解析了,有默认值先用下再说。如果不是,没办法继续抛这个异常。

4.2.7 最后返回包含了所有依赖的 result 数组,我们前面有种情况,如果有自定义构造方法但是没有传入参数,就是 result [] 是空,不影响结果。往下看。

4.3 得到了依赖了,我们使用 newInstanceArgs 方法创建对象不再使用 new 了,依赖 $instances 为参数.

4.3.1 这里再次使用 array_pop 从 buildStack 取出类。我们一开始为什么要存到 buildStack 这个数组中,这个时候可以总结了:

逻辑上来说:就是为了给不一样的逻辑情况提供未污染的变量。简单说就是把这个变量放起来,谁先来谁先用。具体情况来说,为了给解析依赖的时候提供一个及时的当前 build 的 concretion

array_pop($this->buildStack);

return $reflector->newInstanceArgs($instances);

这就是整个流程:

总结:build 方法主要是这样的流程

  1. 是不是闭包,闭包就执行不啰嗦。

  2. 如果不是闭包,利用 php 反射,生成 php 反射对象

  3. 这个类有没有自己定义构造函数,如果没有(构造函数默认的),就直接 new 返回。

  4. 如果有自定义构造函数

4.1 我们先看看有没有在 make 的时候传入第二个参数,有的话获取返回这个依赖。

4.2 没有传入的话,说明不是我们故意要的,有其他的依赖。

a. 看看这个依赖能不能解析,不能解析,我们看看上下文绑定有没有,这个依赖的默认值有没有,都没有,没办法抛出异常。

b. 要是能解析,就直接解析。返回。

4.3 有了依赖了,使用反射通过依赖生成对象。

实例测试:

  1. 直接使用 build,没有依赖的闭包函数
class ExampleTest extends TestCase{

    public function testClosure(){

    $boss= app()->build(function(){return new Money();});

    $output = $boss->getAmount();

    $this->assertEquals($output, 100);

    }

}

class Money{

    public function getAmount(){

        return 100;

    }

}

class Dollar extends Money{

    public function getAmount(){

        return 1;

    }

}
  1. 使用 make 解析,make 通过 build 创建对象,有依赖的闭包函数的测试
class ExampleTest extends TestCase

{

    public function testClosure()

    {

        $this->app->bind('money', function($app, $parameters){

            return new Money($parameters[0]);

        });

        $boss= app()->make('money', [$amount = 1000]);

    }

}

class Money

{

    private $amount = 0;

    public function __construct($amount)
    {

        $this->amount = $amount;

    }

    public function getAmount(){

        return $this->amount;

    }

}
  1. 使用 make 解析,make 通过 build 对象,有依赖的类路径测试。
public function testFunctionBuildWithDependenceOfClass()

{

    $obj = app()->make(Currency::class);

    $this->assertEquals($obj->getAmount(), 1);

}

Class Currency

{

    private $dollar;

    public function __construct(Dollar $dollar)

    {

        $this->dollar = $dollar;

    }

    public function getAmount()

    {

        return $this->dollar->getAmount();

    }

}

class Money

{

    private $amount = 0;

    public function __construct($amount)

    {

        $this->amount = $amount;

    }

    public function getAmount(){

        return $this->amount;

    }

}

class Dollar extends Money

{

    //private $amount = 0;

    public function __construct(){}

    public function getAmount()

    {

        return 1;

    }

}

感想

我们读通源码后,我们可以解决很多找不太到答案的问题,比如:

$obj = app()->make(Money::class, [$amount => 1000]);

class Money

{

    private $amount = 0;

    public function __construct($amount)

    {

        $this->amount = $amount;

    }

    public function getAmount(){

        return $this->amount;

    }

}

尝试解析这个这个类,发现总是提示找不到 amount 依赖,我们追踪下去会发现,with 数组中的 parameter 是

array(1) {
    [0]=>int(1000)
}

由此我们知道我们传入的 parameter 没有 key 值,正确的做法是:

$obj = app()->make(Money::class, ['amount' => 1000]);

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

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值