thinkphp 5.1框架解析(三):容器和依賴注入

在上一篇文章中我們講到了 ThinkPHP 如何實現自動加載,如果想看的話可以看
ThinkPHP5.1 源碼淺析(二)自動加載機制

在閱讀本篇文章 之前,我希望你掌握了 IOC 、DI 、Facade的基本知識,如果不瞭解,請先查看着幾篇文章。

深入理解控制反轉(IoC)和依賴注入(DI)

那麼步入正題。

服務調用

基於分析框架的 入口腳本文件index.php

// 加載基礎文件
require __DIR__ . '/../thinkphp/base.php';

// 支持事先使用靜態方法設置Request對象和Config對象

// 執行應用並響應
Container::get('app')->run()->send();

上面 base.php 中的作用是載入自動加載機制,和異常處理,以及開啓日誌功能。

// 執行應用並響應
Container::get('app')->run()->send();

在這裏纔是使用了 IOC 容器功能,獲取 app 這個容器1

進入 Container 中之後我們先介紹他的類屬性

protected static $instance; // 定義我們的容器類實例,採用單例模式,只實例化一次
protected $instances = [];    // 容器中的對象實例
protected $bind = [];        // 容器綁定標識
protected $name = [];        // 容器標識別名

$instances 是實現了 註冊樹模式2,存儲值

array (
  'think\\App' => App實例,
  'think\\Env' => Env實例,
  'think\\Config' => Config實例,
   ...
)

bind 在初始化時,會載入一堆初始數據,記錄一堆類別名和 類名的映射關係。

protected $bind = [
      'app' => 'think\\App',
      'build' => 'think\\Build',
      'cache' => 'think\\Cache',
      'config' => 'think\\Config',
      'cookie' => 'think\\Cookie',
        ...
    ]

namebind 屬性記錄的值都是很類似的,都是 類別名和 類名的映射關係。區別是,name 記錄的是 已經實例化後的 映射關係。

進入get方法

public static function get($abstract, $vars = [], $newInstance = false)
{
    return static::getInstance()->make($abstract, $vars, $newInstance);
}

這一段代碼沒什麼好講的,就是先獲取當前容器的實例(單例),並實例化。

進入 make 方法

public function make($abstract, $vars = [], $newInstance = false)
{
    if (true === $vars) {
        // 總是創建新的實例化對象
        $newInstance = true;
        $vars        = [];
    }
    // 如果已經存在並且實例化的類,就用別名拿到他的類
    $abstract = isset($this->name[$abstract]) ? $this->name[$abstract] : $abstract;
    // 如果已經實例化,並且不用每次創建新的實例的話,就直接返回註冊樹上的實例
    if (isset($this->instances[$abstract]) && !$newInstance) {
        return $this->instances[$abstract];
    }
    // 如果我們綁定過這個類,例如 'app' => 'think\\App',
    if (isset($this->bind[$abstract])) {
        $concrete = $this->bind[$abstract];
        // 因爲ThinkPHP 實現可以綁定一個閉包或者匿名函數進入,這裏是對閉包的處理
        if ($concrete instanceof Closure) {
            $object = $this->invokeFunction($concrete, $vars);
        } else {
            // 記錄 映射關係,並按照 類名來實例化,如 think\\App
            $this->name[$abstract] = $concrete;
            return $this->make($concrete, $vars, $newInstance);
        }
    } else {
        // 按照類名調用該類
        $object = $this->invokeClass($abstract, $vars);
    }

    if (!$newInstance) {
        $this->instances[$abstract] = $object;
    }
    // 返回製作出來的該類
    return $object;
}

我們拆分一下,

if (true === $vars) {
        // 總是創建新的實例化對象
        $newInstance = true;
        $vars        = [];
}

這段代碼是 讓我們函數可以 使用 make($abstract, true)的方式調用此函數,使我們每次得到的都是新的實例。(我覺得這種方式不是很好,每個變量的造成含義不明確)

// 如果已經存在並且實例化的類,就用別名拿到他的類
    $abstract = isset($this->name[$abstract]) ? $this->name[$abstract] : $abstract;
    // 如果已經實例化,並且不用每次創建新的實例的話,就直接返回註冊樹上的實例
    if (isset($this->instances[$abstract]) && !$newInstance) {
        return $this->instances[$abstract];
    }

前面說過,name 中存放的是已經實例化的 別名=> 類名 的映射關係,我們在這裏嘗試取出 類名,如果該類實例化,就直接返回。

// 如果我們綁定過這個類,例如 'app' => 'think\\App',
    if (isset($this->bind[$abstract])) {
        $concrete = $this->bind[$abstract];
        // 因爲ThinkPHP 實現可以綁定一個閉包或者匿名函數進入,這裏是對閉包的處理
        if ($concrete instanceof Closure) {
            $object = $this->invokeFunction($concrete, $vars);
        } else {
            // 記錄 映射關係,並按照 類名來實例化,如 think\\App
            $this->name[$abstract] = $concrete;
            return $this->make($concrete, $vars, $newInstance);
        }
    } else {
        // 按照類名調用該類
        $object = $this->invokeClass($abstract, $vars);
    }

這裏是看我們需要容器加載的類是否以前綁定過別名(我們也可以直接 bind('classNickName') 來設置一個)

  1. 如果綁定過,那麼就來實例化它。
  2. 如果沒有,那麼就認定他是一個類名,直接調用。3

門面模式

在上面的 IOC 容器中,我們需要 $ioc->get('test'); 才能拿到 test 類,才能使用我們的 $user->hello()方法進行打招呼,有了門面之後,我們可以直接 用 Test::hello() 進行靜態調用,下面我們就來介紹一下這個

在我們編寫代碼時經常會用到 facade包下的類來接口的靜態調用,我們在這裏舉一下官網的例子

假如我們定義了一個app\common\Test類,裏面有一個hello動態方法。

<?php
namespace app\common;

class Test
{
    public function hello($name)
    {
        return 'hello,' . $name;
    }
}

調用hello方法的代碼應該類似於:

$test = new \app\common\Test;
echo $test->hello('thinkphp'); // 輸出 hello,thinkphp

接下來,我們給這個類定義一個靜態代理類app\facade\Test(這個類名不一定要和Test類一致,但通常爲了便於管理,建議保持名稱統一)。

<?php
namespace app\facade;

use think\Facade;

class Test extends Facade
{
    protected static function getFacadeClass()
    {
        return 'app\common\Test';
    }
}

只要這個類庫繼承think\Facade,就可以使用靜態方式調用動態類app\common\Test的動態方法,例如上面的代碼就可以改成:

// 無需進行實例化 直接以靜態方法方式調用hello
echo \app\facade\Test::hello('thinkphp');

結果也會輸出 hello,thinkphp

說的直白一點,Facade功能可以讓類無需實例化而直接進行靜態方式調用。

Facade工作原理

  1. Facede 核心實現原理就是在 Facade 提前注入IoC容器。
  2. 定義一個服務提供者的外觀類,在該類定義一個類的變量,跟ioc容器綁定的key一樣,
  3. 通過靜態魔術方法__callStatic可以得到當前想要調用的 hello 方法
  4. 使用static::$ioc->make('Test');

爲什麼要使用 Facade

使用Facades其實最主要的就是它提供了簡單,易記的語法,從而無需手動注入或配置長長的類名。此外,由於他們對 PHP 靜態方法的獨特調用,使得測試起來非常容易。


  1. 在這裏系統找不到 Container 類的位置,所以會執行自動加載機制去尋找 Container 的位置,並加載它
  2. 把一堆實例掛在樹上,需要的時候在拿來用。
  3. 直接調用是使用了反射後的結果,關於反射的知識點在自行查看
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章