Yii2中關於組件的註冊以及建立的方法詳解

瞭解yii組件註冊與建立的過程,並發現原來yii組件註冊以後並非立刻就去建立的,而是待到實際須要使用某個組件的時候再去建立對應的組件實例的。本文大概記錄一下這個探索的過程。php

  要了解yii組件的註冊與建立,固然要從yii入口文件index.php提及了,整個文件代碼以下:html

<?phpweb

defined('YII_DEBUG') or define('YII_DEBUG', true);數據庫

defined('YII_ENV') or define('YII_ENV', 'dev');bootstrap

require(DIR . '/../../vendor/autoload.php');swift

require(DIR . '/../../vendor/yiisoft/yii2/Yii.php');數組

require(DIR . '/../../common/config/bootstrap.php');yii2

require(DIR . '/../config/bootstrap.php');併發

$config = yii\helpers\ArrayHelper::merge(app

 require(DIR . '/../../common/config/main.php'),

 require(DIR . '/../../common/config/main-local.php'),

 require(DIR . '/../config/main.php'),

 require(DIR . '/../config/main-local.php')

);

(new yii\web\Application($config))->run();

能夠看到入口文件引入了幾個配置文件,並將全部配置文件的內容都合併到$config這個配置數組中,而後使用這個配置數組做爲參數去建立一個應用實例。若將這個配置數組打印出來,就會看到,「components」下標對應的元素包含了yii組件的參數信息(這裏只截圖一小部分):

這些組件的信息是在引入進來的幾個配置文件中配置的,Yii組件就是使用這些參數信息進行註冊與建立的。

  接下來就進入yii\web\Application類的實例化過程了,yii\web\Application類沒有構造函數,可是它繼承了\yii\base\Application類:

因此會自動執行\yii\base\Application類的構造函數:

public function construct($config = [])

{

 Yii::$app = $this;

 static::setInstance($this);

 $this->state = self::STATE_BEGIN;

 $this->preInit($config);

 $this->registerErrorHandler($config);

 Component::construct($config);

}

這裏要順便說一下預初始化方法preInit(),它的代碼以下:

public function preInit(&$config)

{

 /* 此處省略對$config數組的預處理操做代碼 */

 // merge core components with custom components

 foreach ($this->coreComponents() as $id => $component) {

  if (!isset($config['components'][$id])) {

   $config['components'][$id] = $component;

  } elseif (is_array($config['components'][$id]) && !isset($config['components'][$id]['class'])) {

   $config['components'][$id]['class'] = $component['class'];

  }

 }

}

  這個函數對傳遞給構造函數的配置數組$config進行了一些預處理操做(這裏省略了),最後使用coreComponents()方法返回的數組對$config數組進行了完善,coreComponents()方法是這樣的:

public function coreComponents()

{

 return [

  'log' => ['class' => 'yii\log\Dispatcher'],

  'view' => ['class' => 'yii\web\View'],

  'formatter' => ['class' => 'yii\i18n\Formatter'],

  'i18n' => ['class' => 'yii\i18n\I18N'],

  'mailer' => ['class' => 'yii\swiftmailer\Mailer'],

  'urlManager' => ['class' => 'yii\web\UrlManager'],

  'assetManager' => ['class' => 'yii\web\AssetManager'],

  'security' => ['class' => 'yii\base\Security'],

 ];

}

  其實就是一些核心組件的配置,也就是說這些組件是能夠不須要咱們在配置文件中配置的,yii會自動進行註冊。

  好了,回到\yii\base\Application類的構造函數,這個函數最後調用了\yii\base\Component類的構造函數,但\yii\base\Component類是沒有構造函數的,不過它繼承了\yii\base\Object類:

因此也自動執行了\yii\base\Object類的構造函數:

public function construct($config = [])

{

 if (!empty($config)) {

  Yii::configure($this, $config);

 }

 $this->init();

}

這裏主要是調用了\yii\BaseYii類的靜態方法configure():

public static function configure($object, $properties)

{

 foreach ($properties as $name => $value) {

  $object->$name = $value;

 }

 return $object;

}

這個方法就是循環入口文件(new yii\web\Application($config))->run();中的$config數組(這個數組的結構參見本文第一個截圖),以數組鍵名做爲對象屬性名,對應的鍵值做爲對象屬性值進行賦值操做。因此當循環到組件配置參數的時候是這樣子的:$object->components = $value($value爲全部組件的配置數組),也就是對$object的components屬性進行賦值操做,那這個$object是哪一個類的對象呢?回想最初調用的源頭,其實它就是入口文件中須要進行實例化的\yii\web\Application類的對象啊。然而,這個類和它的祖先類都沒有components這個成員變量啊,不急,又要進行一番繼承套路了,順着yii\web\Application類的繼承關係一層一層往上找能夠發現\yii\web\Application類最終也繼承了\yii\base\Object類,\yii\base\Object類是支持屬性的,因此yii\web\Application類也支持屬性(關於屬性,能夠參考個人另外一篇博文:yii2之屬性),當賦值操做找不到components成員變量時會調用setComponents()方法,又去找這個方法的所在,終於在它的祖先類\yii\di\ServiceLocator中找到了setComponents()方法,沒錯,對應用實例的components屬性進行賦值操做其實就是調用這個方法!

  好了,如今就來看看setComponents()這個方法到底幹了啥:

public function setComponents($components)

{

 foreach ($components as $id => $component) {

  $this->set($id, $component);

 }

}

其實很簡單,就是循環各個組件的配置數組,調用set()方法,set()方法以下:

public function set($id, $definition)

{ unset($this->_components[$id]);

 if ($definition === null) {

  unset($this->_definitions[$id]);

  return;

 }

 if (is_object($definition) || is_callable($definition, true)) {

  // an object, a class name, or a PHP callable

  $this->_definitions[$id] = $definition;

 } elseif (is_array($definition)) {

  // a configuration array

  if (isset($definition['class'])) {

   $this->_definitions[$id] = $definition;

  } else {

   throw new InvalidConfigException("The configuration for the \"$id\" component must contain a \"class\" element.");

  }

 } else {

  throw new InvalidConfigException("Unexpected configuration type for the \"$id\" component: " . gettype($definition));

 }

}

其實就是把組件配置存入$_definitions這個私有成員變量(即註冊),而後呢?而後就沒有下文了。。。

  搞了半天,原來yii建立應用實例的時候只是進行組件的註冊,並無實際建立組件,那麼組件實例是何時進行建立的?在哪裏進行建立的呢?別急。從上面推導的這個過程咱們知道\yii\di\ServiceLocator類是\yii\web\Application類的祖先類,因此其實yii的應用實例其實就是一個服務定位器,好比咱們想訪問數據庫組件的時候,咱們能夠這樣來訪問:Yii::$app->db,這個Yii::$app就是yii應用實例,也就是\yii\web\Application類的實例,可是\yii\web\Application類和它的父類、祖先類都找不到db這個屬性啊。哈哈,別忘了,php讀取不到類屬性的時候會調用魔術方法get(),因此開始查找\yii\web\Application繼承關係最近的祖先類中的get()方法,最後在\yii\di\ServiceLocator類中找到了,也就是說,Yii::$app->db最終會調用\yii\di\ServiceLocator類中的get()方法:

public function get($name)

{

 if ($this->has($name)) {

  return $this->get($name);

 } else {

  return parent::get($name);

 }

}

get()方法首先調用has()方法(這個再也不貼代碼了)判斷組件是否已註冊,若已註冊則調用get()方法:

public function get($id, $throwException = true)

{

 if (isset($this->_components[$id])) {

  return $this->_components[$id];

 }

 if (isset($this->_definitions[$id])) {

  $definition = $this->_definitions[$id];

  if (is_object($definition) && !$definition instanceof Closure) {

   return $this->_components[$id] = $definition;

  } else {

   return $this->_components[$id] = Yii::createObject($definition);

  }

 } elseif ($throwException) {

  throw new InvalidConfigException("Unknown component ID: $id");

 } else {

  return null;

 }

}

其中私有成員變量$_components是存儲已經建立的組件實例的,若發現組件已經建立過則直接返回組件示例,不然使用$_definitions中對應組件的註冊信息,調用\yii\BaseYii::createObject()方法進行組件建立,這個方法最終會調用依賴注入容器\yii\di\Container的get()方法,接着就是依賴注入建立對象的過程了,關於這個過程已經在個人上一篇博文中講解過了,能夠參考一下:yii2之依賴注入與依賴注入容器。

  好了,yii組件註冊與建立的整個過程就是這樣的。最後總結一下,其實yii建立應用實例的時候只是進行了各個組件的註冊,也就是將組件的配置信息存入\yii\di\ServiceLocator類的私有成員變量$_definitions中,並無進行實際建立,等到程序運行過程當中真正須要使用到某個組件的時候才根據該組件在$_definitions中保存的註冊信息使用依賴注入容器\yii\di\Container進行組件實例的建立,而後把建立的實例存入私有成員變量$_components,這樣下次訪問相同組件的時候就能夠直接返回組件實例,而再也不須要執行建立過程了。yii的這個組件註冊與建立機制實際上是大有裨益的,試想一下,若是在應用實例建立的時候就進行全部組件的建立,將會大大增長應用實例建立的時間,用戶每次刷新頁面都會進行應用實例的建立的,也就是說用戶每刷新一次頁面都很慢,這用戶體驗就很很差了,並且不少狀況下有不少組件實際上是沒有使用到的,可是咱們仍是花了很多時間去建立這些組件,這是很不明智的,因此yii的作法就是:先把組件參數信息保存起來,須要使用到哪些組件再去建立相應的實例,大大節省了應用建立的時間,同時也節省了內存,這種思路是很值得咱們學習的!

 
 
G
M
T
 
 
Detect languageAfrikaansAlbanianArabicArmenianAzerbaijaniBasqueBelarusianBengaliBosnianBulgarianCatalanCebuanoChichewaChinese (Simplified)Chinese (Traditional)CroatianCzechDanishDutchEnglishEsperantoEstonianFilipinoFinnishFrenchGalicianGeorgianGermanGreekGujaratiHaitian CreoleHausaHebrewHindiHmongHungarianIcelandicIgboIndonesianIrishItalianJapaneseJavaneseKannadaKazakhKhmerKoreanLaoLatinLatvianLithuanianMacedonianMalagasyMalayMalayalamMalteseMaoriMarathiMongolianMyanmar (Burmese)NepaliNorwegianPersianPolishPortuguesePunjabiRomanianRussianSerbianSesothoSinhalaSlovakSlovenianSomaliSpanishSundaneseSwahiliSwedishTajikTamilTeluguThaiTurkishUkrainianUrduUzbekVietnameseWelshYiddishYorubaZulu
 
AfrikaansAlbanianArabicArmenianAzerbaijaniBasqueBelarusianBengaliBosnianBulgarianCatalanCebuanoChichewaChinese (Simplified)Chinese (Traditional)CroatianCzechDanishDutchEnglishEsperantoEstonianFilipinoFinnishFrenchGalicianGeorgianGermanGreekGujaratiHaitian CreoleHausaHebrewHindiHmongHungarianIcelandicIgboIndonesianIrishItalianJapaneseJavaneseKannadaKazakhKhmerKoreanLaoLatinLatvianLithuanianMacedonianMalagasyMalayMalayalamMalteseMaoriMarathiMongolianMyanmar (Burmese)NepaliNorwegianPersianPolishPortuguesePunjabiRomanianRussianSerbianSesothoSinhalaSlovakSlovenianSomaliSpanishSundaneseSwahiliSwedishTajikTamilTeluguThaiTurkishUkrainianUrduUzbekVietnameseWelshYiddishYorubaZulu
 
 
 
 
 
 
 
 
 
Text-to-speech function is limited to 200 characters
 
 
Options : History : Feedback : Donate Close
相關文章
相關標籤/搜索