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

在上一篇文章中咱們講到了 ThinkPHP 如何實現自動加載,若是想看的話能夠看
ThinkPHP5.1 源碼淺析(二)自動加載機制php

在閱讀本篇文章 以前,我但願你掌握了 IOC 、DI 、Facade的基本知識,若是不瞭解,請先查看着幾篇文章。thinkphp

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

那麼步入正題。cookie

服務調用

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

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

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

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

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

// 執行應用並響應
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 在初始化時,會載入一堆初始數據,記錄一堆類別名和 類名的映射關係。ui

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. 直接調用是使用了反射後的結果,關於反射的知識點在自行查看
相關文章
相關標籤/搜索