yii2 隨筆(七)依賴注入——(3)yii2的依賴注入

來源:互聯網
上載者:User
yii2的依賴注入的核心代碼在 yii\di,在這個包(檔案夾)下面有3個檔案,分別是Container.php(容器),Instance.php(執行個體),ServiceLocator(服務定位器),現在我們討論一下前兩個,服務定位器可以理解一個服務的註冊表,這個不影響我們討論依賴注入,它也是依賴注入的一種應用。

我們還是從代碼開始講解yii2是怎麼使用依賴注入的。

// yii\base\application//這個是yii2的依賴注入使用入口,參數的解釋請參考源碼,這裡不多解釋public static function createObject($type, array $params = []){    if (is_string($type)) {//type 是字串的話,它就把type當做一個對象的“原材料”,直接把它傳給容器並通過容器得到想要的對象。        return static::$container->get($type, $params);    } elseif (is_array($type) && isset($type['class'])) {    //type 是數組,並且有class的鍵,經過簡單處理後,得到對象的“原材料”,然後把得到的“原材料”傳給容器並通過容器得到想要的對象。        $class = $type['class'];        unset($type['class']);        return static::$container->get($class, $params, $type);    } elseif (is_callable($type, true)) {//如果type是可調用的結構,就直接調用        return call_user_func($type, $params);    } elseif (is_array($type)) {//如果type是array,並且沒有'class'的索引值,那麼就拋出異常        throw new InvalidConfigException('Object configuration must be an array containing a "class" element.');    } else {//其他情況,均拋出另一個異常,說type不支援的配置類型        throw new InvalidConfigException("Unsupported configuration type: " . gettype($type));    }}

通過閱讀上面代碼,Yii::createObject()是把合格的“原材料”,交給“容器($container)”,來產生目標對象的,那麼容器就是我們“依賴注入”生產對象的地方。那麼$container是什麼時候引入的呢(注意這裡用的是 static::$container, 而不是 self::$container)?還記得在首頁匯入yii架構時的語句嗎?

//匯入yii架構require(__DIR__ . '/../vendor/yiisoft/yii2/Yii.php');

代碼如下

//引入基本的yii架構require(__DIR__ . '/BaseYii.php');//只是做了繼承,這裡給我們留了二次開發的餘地,雖然很少能用到class Yii extends \yii\BaseYii{}//設定自動載入spl_autoload_register(['Yii', 'autoload'], true, true);//註冊 classMapYii::$classMap = require(__DIR__ . '/classes.php');//註冊容器Yii::$container = new yii\di\Container();

你看的沒錯!就是最後一句話,yii2 把 yii\di\Container 的實現拿給自己使用。接下來,我們討論一下容器是怎麼實現的?

接著上面的 static::$container->get() 的方法,在講解get方法之前,我們要先瞭解一下容器的幾個屬性,這將有助於理解get的實現

$_singletons; // 單例數組,它的索引值是類的名字,如果產生的對象是單例,則把他儲存到這個數組裡,值為null的話,表示它還沒有被執行個體化$_definitions;// 定義數組,它的索引值是類的名字,值是產生這個類所需的“原材料”,在set 或 setSingleton的時候寫入$_params; // 參數,它的索引值是類的名字,值是產生這個類所需的額外的“原材料”,在set 或 setSingleton的時候寫入$_reflections; //反射,它的索引值是類的名字,值是要產生的對象的反射控制代碼,在產生對象的時候寫入$_dependencies;//依賴,它的索引值是類的名字,值是要產生對象前的一些必備“原材料”,在產生對象的時候,通過反射函數得到。

ok,如果你夠細心地話,理解了上面的幾個屬性,估計你就對yii2的容器有個大概的瞭解了,這裡還是從get開始。

public function get($class, $params = [], $config = []){    if (isset($this->_singletons[$class])) {//查看將要產生的對象是否在單例裡,如果是,則直接返回        // singleton        return $this->_singletons[$class];    } elseif (!isset($this->_definitions[$class])) {//如果沒有要產生類的定義,則直接產生,yii2自身大部分走的是這部分,並沒有事先在容器裡註冊什麼,    那麼設定檔是在哪裡註冊呢?還記的文章最開始的時候的"服務定位器"嗎?我們在服務定位器裡講看到這些。        return $this->build($class, $params, $config);    }    //如果已經定義了這個類,則取出這個類的定義    $definition = $this->_definitions[$class];    if (is_callable($definition, true)) {//如果定義是可調用的結構        //先整合一下參數,和$_params裡是否有這個類的參數,如果有則和傳入的參數以傳入覆蓋定義的方式整和在一起        //然後再檢查整合後的參數是否符合依賴,就是說是否有必填的參數,如果有直接拋出異常,否則返回參數。檢查依賴的時候,需要判斷是否為執行個體(Instance),如果是,        則要實現執行個體。注意:這裡出現了Instance。        $params = $this->resolveDependencies($this->mergeParams($class, $params));        //把參數專遞給可調用結果,返回結果        $object = call_user_func($definition, $this, $params, $config);    } elseif (is_array($definition)) {//如果定義是一個數組        //把代表要產生的class取出        $concrete = $definition['class'];        //登出這個索引值        unset($definition['class']);        //把定義 和 配置整合成新的定義        $config = array_merge($definition, $config);        //整合參數        $params = $this->mergeParams($class, $params);        //如果傳入的$class 和 定義裡的class完全一樣,則直接產生,build第一個參數確保為真實的類名,而傳入的$type可能是別名        if ($concrete === $class) {            $object = $this->build($class, $params, $config);        } else {//如果是別名,則回調自己,產生對象,因為這時的類也有可能是別名            $object = $this->get($concrete, $params, $config);        }    } elseif (is_object($definition)) {//如果定義是一個對象,則代表這個類是個單例,儲存到單例裡,並返回這個單例,這裡要自己動腦想一下,    為什麼是個對象就是單例?只可意會不可言傳,主要是我也組織不好語言怎麼解釋它。        return $this->_singletons[$class] = $definition;    } else {//什麼都不是則拋出異常        throw new InvalidConfigException("Unexpected object definition type: " . gettype($definition));    }    //判斷這個類的名字是否在單例裡,如果在,則把產生的對象放到單例裡    if (array_key_exists($class, $this->_singletons)) {        // singleton        $this->_singletons[$class] = $object;    }    //返回產生的對象    return $object;}

研究到這裡,我們發現 get 函數僅僅是個“入口”而已,主要的功能在build裡

//建立對象protected function build($class, $params, $config){    //通過類名得到反射控制代碼,和依賴(依賴就是所需參數)    //所以前面提到,傳輸buile的第一個參數必須為有效“類名”否則,會直接報錯    list ($reflection, $dependencies) = $this->getDependencies($class);    //把依賴和參數配置,因為依賴可能有預設參數,這裡覆蓋預設參數    foreach ($params as $index => $param) {        $dependencies[$index] = $param;    }    //確保依賴沒問題,所有原材料是否都ok了,否則拋出異常    $dependencies = $this->resolveDependencies($dependencies, $reflection);    if (empty($config)) {//如果config為空白,則返回目標對象        return $reflection->newInstanceArgs($dependencies);    }        if (!empty($dependencies) && $reflection->implementsInterface('yii\base\Configurable'))     {//如果目標對象是 Configurable的介面        // set $config as the last parameter (existing one will be overwritten)        $dependencies[count($dependencies) - 1] = $config;        return $reflection->newInstanceArgs($dependencies);    } else {//其他的情況下        $object = $reflection->newInstanceArgs($dependencies);        foreach ($config as $name => $value) {            $object->$name = $value;        }        return $object;    }}

好了,build到這裡就結束了,下面我們一起看看容器是怎麼得到反射控制代碼和依賴關係的

protected function getDependencies($class){    if (isset($this->_reflections[$class])) {//是否已經解析過目標對象了        return [$this->_reflections[$class], $this->_dependencies[$class]];    }       $dependencies = [];//初始化依賴數組    $reflection = new ReflectionClass($class);//得到目標對象的反射,請參考php手冊    $constructor = $reflection->getConstructor();//得到目標對象的建構函式    if ($constructor !== null) {//如果目標對象有建構函式,則說明他有依賴        //解析所有的參數,注意得到參數的順序是從左至右的,確保依賴時也是按照這個順序執行        foreach ($constructor->getParameters() as $param) {            if ($param->isDefaultValueAvailable()) {//如果參數的預設值可用                $dependencies[] = $param->getDefaultValue();//把預設值放到依賴裡            } else {//如果是其他的                $c = $param->getClass();//得到參數的類型,如果參數的類型不是某類,是基本類型的話,則返回null                //如果,是基本類型,則產生null的執行個體,如果不是基本類型,則產生該類名的執行個體。                注意:這裡用到了執行個體(Instance)                $dependencies[] = Instance::of($c === null ? null : $c->getName());            }        }    }    //把引用儲存起來,以便下次直接使用    $this->_reflections[$class] = $reflection;    //把依賴存起來,以便下次直接使用    $this->_dependencies[$class] = $dependencies;    //返回結果    return [$reflection, $dependencies];}

下面我們來看看容器是怎麼確保依賴關係的

protected function resolveDependencies($dependencies, $reflection = null){    //拿到依賴關係    foreach ($dependencies as $index => $dependency) {        //如果依賴是一個執行個體,因為經過處理的依賴,都是Instance的對象        if ($dependency instanceof Instance) {            if ($dependency->id !== null) {//這個執行個體有id,則通過這個id產生這個對象,並且代替原來的參數                $dependencies[$index] = $this->get($dependency->id);            } elseif ($reflection !== null) {//如果反射控制代碼不為空白,注意這個函數是protected 類型的,            所以只有本類或者本類的衍生類可訪問,但是本類裡只有兩個地方用到了,一個是 get 的時候,            如果目標對象是可調用的結果(is_callable),那麼$reflection===null,另外一個build的時候,            $reflection不為空白,這個時候代表目標對象有一個必須參數,但是還不是一個執行個體(Instance的對象),            這個時候代表缺乏必須的“原材料”拋出異常                //則拿到響應的必填參數名字,並且拋出異常                $name = $reflection->getConstructor()->getParameters()[$index]->getName();                $class = $reflection->getName();                throw new InvalidConfigException("Missing required parameter \"$name\" when instantiating \"$class\".");            }        }    }    //確保了所有的依賴後,返回所有依賴,如果目標是is_callable($definition, true),則不會拋出異常,僅僅把Instance類型的參數執行個體化出來。    return $dependencies;}

看到這裡,我們就可以瞭解了yii2是怎麼使用容器實現“依賴注入”了,那麼有個問題,閉包的依賴怎麼保證呢?我想是因為yii2認為閉包的存在解決的是局限性的問題,不存在依賴性,或者依賴是交給開發人員自行解決的。另外yii2的容器,如果參數是閉包的話,就會出現錯誤,因為對閉包的依賴,解析閉包參數的時候,會得到$dependencies[]
= Instance::of($c === null ? null : $c->getName());得到的就是一個 Closure 的執行個體,而後面 執行個體化這個執行個體的時候,就會出現問題了,所以用yii2的容器實現對象的時候,被實現的對象不能包含閉包參數,如果有閉包參數,則一定要有預設值,或者人為保證會傳入這個閉包參數,繞過自動產生的語句。

ok容器的主要函數就有這些了,其他方法,set,setSingleton,has,hasSingleton,clear一看就知道什麼意思,另外這些方法基本上沒有在架構中使用(可以在這些函數寫exit,看看你的頁面會不會空白),或者你用容器自己產生一些東西的話,可以自行查看這些函數的用法。

最後,我們來看看Instance到底扮演了什麼角色

//yii\di\Instance//很詫異吧,就是執行個體化一個自己,注意這個自己是 static,以後你可能需要用到這個地方public static function of($id){    return new static($id);}[/php]那麼這個函數的建構函式呢?[php]//禁止外部執行個體化protected function __construct($id){    //賦值id    $this->id = $id;}

在容器中,就用到了Instance的這兩個方法,說明Instance在執行個體中,只是確保了依賴的可用性。此外Instance還提供了其他的函數,其中 get 得到的是當前Instance所對應的id的執行個體化對象,另外,還有一個靜態函數ensure

//確保 $reference 是 $type類型的,如果不是則拋出異常//在架構中多次用到,請自行尋找//另外,如果$type==null的時候,他也可以當做依賴注入的入口,使用方法請自行查看源碼,到現在你應該可以自己看懂這些代碼了。public static function ensure($reference, $type = null, $container = null){    //...}

以上就是yii2 隨筆(七)依賴注入——(3)yii2的依賴注入的內容,更多相關內容請關注topic.alibabacloud.com(www.php.cn)!

  • 聯繫我們

    該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

    如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

    A Free Trial That Lets You Build Big!

    Start building with 50+ products and up to 12 months usage for Elastic Compute Service

    • Sales Support

      1 on 1 presale consultation

    • After-Sales Support

      24/7 Technical Support 6 Free Tickets per Quarter Faster Response

    • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.