1.6 - Laravel - 5.6 - Container Bind 机制

理解了前面部分的机制后,我们开始来阅读源码

首先从Bind绑定开始。

Illuminate\Container, Container最重要的方法:bind

绑定分为几种:

1. bind 把接口和其实现类绑定,当make解析接口的时候创建其实现类的实例对象。

2. single 把接口和其实现类绑定,当第一次make解析的时候创建实例,后面都返回该实例不再创建。

3. instance 把接口和其实现类的实例绑定,直接绑定实例对象。

4. 上下文绑定

5. 自动绑定

6. tag绑定

7. extends扩展绑定

先大概看bind下源代码:


public function bind($abstract, $concrete = null, $shared = false)
{
    $this->dropStaleInstances($abstract);
    if (is_null($concrete)) {
        $concrete = $abstract;
    }

    if (! $concrete instanceof Closure) {
        $concrete = $this->getClosure($abstract, $concrete);
    }

    $this->bindings[$abstract] = compact('concrete', 'shared');

    if ($this->resolved($abstract)) {
        $this->rebound($abstract);
    }
}

参数:

0.1 首先明确第一个参数 $abstruct 简单说就是id,可以当做是存入容器中的名字。他可以是一个字符串,一个类,甚至是一个接口。

0.2 第二个参数 $concrete 简单说就是真实的值,可以当做是一个真正存入容器的实体。他可以是一个实现类,实例,或者一个闭包(闭包可以返回一个实现类的实例)。

0.3 第三个参数控制Shared的值。


方法体:

1. 绑定前,先清空instances和aliases中存在的同名字的服务。

$this->dropStaleInstances($abstract);

1.1.dropStaleInstances($abstract)如下,就是清空当前instance中和aliases中存在的$abstruct同名的服务。


protected function dropStaleInstances($abstract)
{
    unset($this->instances[$abstract], $this->aliases[$abstract]);
}

2. 然后判断第二个参数$concrete是不是空,如果是空,就视$abstruct$concrete一样。比如: app()->bind(Boss::class)


if (is_null($concrete))
{
    $concrete = $abstract;
}

3.如果当前这个$concrete不是一个闭包。就调用getClosure,返回一个闭包便于后面的操作。

if (! $concrete instanceof Closure)
{
    $concrete = $this->getClosure($abstract, $concrete);
}

3.1.我们去看看这个getClosure方法是怎么返回一个闭包的。

很简单代码最后直接返回了一个这样形式的闭包:function($container,$parameters=[])

一些细节:

使用use关键字调用父类就是getClosure传入的$abstruct$concrete两个参数。

如果$abstruct$concrete是一样的,就是如果只有一个参数,或者确实两个参数一样,像这样 app->bind(User::class,User::class),那么就调用build方法。

否则使用make方法。(build方法和make方法,参看后面章节)

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($container,$parameters=[])。赋值给变量$concrete。而这个闭包内返回的是通过build或者make解析的值


4.我们回到bind方法,上面$concrete得到一个闭包函数后,调用compact把$concrete$shard (第三个参数判断是否shared)组成一个key分别为concrete和shared的数组,存入binding数组中,而binding数组的key是当前的抽象类。


$this->bindings[$abstract] = compact('concrete', 'shared');

处理后结构是这样的:


$binding[$abstract] => [
    'concrete' => function($container,$parameters=[]),//getClosure()得到的
    'shared' => true/false,//shared的值是bind的第三个参数
]

5.接下来下一句,如果当前的抽象类曾经被解析过。那再次绑定的时候,我们要使用rebound函数触发reboundCallbacks数组中的回调函数。

关于回调函数参看前面章节


if ($this->resolved($abstract))
{
    $this->rebound($abstract);
}

如何判断当前的$abstruct曾经被解析过呢,我们看下resolved函数。两个条件

1.简单判断当前resolved数组中是否存在$abstruct

2.或者instances数组中是存在对应的值。但我们注意,先前在bind方法的第一句$this->dropStaleInstances($abstract);的时候我们清空了instances对应的$abstruct的值,所以这边主要是考虑$abstruct的别名在instances中是否存在残留的情况。


public function resolved($abstract)
{
    if ($this->isAlias($abstract)) {
        $abstract = $this->getAlias($abstract);
    }

    return isset($this->resolved[$abstract]) ||
isset($this->instances[$abstract]);
}

总结:

在bind方法中。

1.首先移除旧的实例,如果参数$concrete不是闭包,是类名,会通过getClosure函数将类名封装进闭包中,返回这个闭包。总之container就要闭包。

注意:build和make都是在一个闭包函数中,闭包函数不触发,它是不会创建对象的。也就是所谓的懒加载。关于build和make他是如果操作的,下几章讲解。

2.然后把返回的闭包函数和share的值组合放入$this->bindings数组中。

3.最后判断当前这个$abstruct是否以前被解析过,如果是,要触发对应的回调函数。

**最简单的来说

就是原来在容器中,绑定的是一个id和一个闭包函数的组合。你传入闭包最好,不是闭包,laravel会转成闭包存起来。

暂时从代码来看,我们可以猜想,最后从容器解析出来的对象是运行这个闭包产生返回的。

那我们就会有这样的猜想了,我们可以通过闭包绑定任何类型的值,因为只要在闭包中返回我们想要的任何类型的值就好了。**

实例测试

测试1:使用闭包函数返回有依赖的对象。


class Money
{
    private $amount = 0;
    public function __construct($amount)
    {
        $this->amount = $amount;
    }

    public function getAmount()
    {
        return $this->amount;
    }
}

//注意闭包的形式

$this->app->bind('money', function($app, $parameters){return new Money($parameters[0]);});

测试2:使用闭包函数返回没有依赖的对象

class Dollar
{
    public function getAmount(){
        return 100;
    }
}

$this->app->bind('dollar', function(){return new Dollar();});
本作品采用《CC 协议》,转载必须注明作者和本文链接
《L01 基础入门》
我们将带你从零开发一个项目并部署到线上,本课程教授 Web 开发中专业、实用的技能,如 Git 工作流、Laravel Mix 前端工作流等。
《G01 Go 实战入门》
从零开始带你一步步开发一个 Go 博客项目,让你在最短的时间内学会使用 Go 进行编码。项目结构很大程度上参考了 Laravel。
讨论数量: 0
(= ̄ω ̄=)··· 暂无内容!

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