1.6 - Laravel - 5.6 - Container Bind 機制

HarveyNorman發表於2020-03-07

理解了前面部分的機制後,我們開始來閱讀原始碼

首先從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 協議》,轉載必須註明作者和本文連結

相關文章