1.1 - Laravel5.6 - Contextual 上下文繫結機制

HarveyNorman發表於2020-02-28

上下文繫結在分析Container原始碼的時候是一個比較重要的部分,在瞭解上下文繫結之前,先解釋下什麼是上下文:

每一段程式都有很多外部變數。只有像 Add 這種簡單的函式才是沒有外部變數的。一旦你的一段程式有了外部變數,這段程式就不完整,不能獨立執行。你為了使他們執行,就要給所有的外部變數一個一個寫一些值進去。這些值的集合就叫上下文。 「程式設計中什麼是「Context (上下文)」?」 - vczh 的回答。

簡單說就是解析一個物件的時候,有些物件是需要外部的一些依賴的。那他在建立的時候就要用到上下文把依賴引入。

上下文繫結的意思就是專門處理·例項化時候有依賴關係·情況的一種繫結

上下文繫結在Laravel 5.6文件中給出了相關示例:

use Illuminate\Support\Facades\Storage;
use App\Http\Controllers\PhotoController;
use App\Http\Controllers\VideoController;
use Illuminate\Contracts\Filesystem\Filesystem;

$this->app->when(PhotoController::class)
          ->needs(Filesystem::class)
          ->give(function () {
              return Storage::disk('local');
          });

$this->app->when(VideoController::class)
          ->needs(Filesystem::class)
          ->give(function () {
              return Storage::disk('s3');
          });

這是專案中常會用到儲存功能,得益於 Laravel 內建整合了 FlySystemFilesystem 介面,我們很容易實現多種儲存服務的專案。

示例中將使用者頭像儲存到本地,將使用者上傳的小視訊儲存到雲服務。那麼這個時就需要區分這樣不同的使用場景(即上下文或者說環境)。

當使用者儲存頭像(PhotoController::class)需要使用儲存服務(Filesystem::class)時,我們將本地儲存驅動,作為實現給到 PhotoController::class:

function () {
    return Storage::disk('local');
}

而當使用者上傳視訊 VideoController::class,需要使用儲存服務(Filesystem::class)時,我們則將雲服務驅動,作為實現給到 VideoController::class:

function () {
    return Storage::disk('s3');
}

原始碼:
我們來看下原始碼的實現。
illuminate\Container\Container.php
1.看下when方法,這個方法直接生成一個ContextualBindingBuilder物件,傳入container物件和$concrete
$concrete在這個例子中就是PhotoController::classVideoController::class
我們暫且用PhotoController::class為例

public function when($concrete)
{
    return new ContextualBindingBuilder($this, $this->getAlias($concrete));
}

2.然後進入這個ContextualBindingBuilder類看下。
這個類不大,提供了兩個方法,needs和give
先看下needs方法,很簡單就是把$abstruct儲存起來。
這個例子中的$abstruct就是Filesystem::class類
然後返回當前物件,以致可以繼續鏈式操作。

public function needs($abstract)
    {
        $this->needs = $abstract;

        return $this;
    }

3.再看下give方法,也很簡單又重新呼叫了$container中的addContextualBinding(),這個就是新增上下文繫結的方法,分別傳入的是:
concrete:PhotoController::class的別名(如果有的話)
abstruct:Filesystem::class
implemention:閉包Storage::disk('local');的返回值。

public function give($implementation)
{
    $this->container->addContextualBinding(
        $this->concrete, $this->needs, $implementation
    );
}

4.然後我們回到Container看看方法addContextualBinding().
也很簡單,就是把這些引數存入contextual陣列。

(這個特別重要)陣列結構形式為:
contextual[PhotoController::class][Filesystem::class] = 閉包函式(也可以是一個類路徑)

public function addContextualBinding($concrete, $abstract, $implementation)
    {
        $this->contextual[$concrete][$this->getAlias($abstract)] = $implementation;
    }

這就完成了一個上下文繫結。說到底和普通繫結雷同只不過是處理有依賴的物件。

總結
當一個類需要一些外部依賴的時候,要用到上下文繫結。把外部依賴傳遞給他。
實現方式就是存入到容器中那個負責上下文的那個陣列中,這個陣列將會在解析的時候(就是取出某個物件的時候,他對應繫結的依賴也會被取出)做判斷。

實測用例

public function testClosure(){
    $this->app->when(Boss::class)
        ->needs(Money::class)
        ->give(Cheque::class);

    $boss= app()->make(Boss::class);
    $output = $boss->getA();
    $this->assertEquals($output, 100000);
}


//interface
interface Money
{
    public function getAmount();
}

class Dollar implements Money
{
    public function getAmount()
    {
        return 1;
    }
}

class Cheque implements Money
{
    public function getAmount()
    {
        return 100000;
    }
}


class Worker
{
    private $money;

    public function __construct(Money $money)
    {
        $this->money = $money; // prints '1'
    }

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

class Boss
{
    private $money;

    public function __construct(Money $money)
    {
        $this->money = $money; // prints '100000'
    }

    public function getA(){
        return $this->money->getAmount();
    }
}
本作品採用《CC 協議》,轉載必須註明作者和本文連結

相關文章