Yii2中組件的注冊(cè)與創(chuàng)建方法
今天本來打算研究一下yii2.0的AR模型的實(shí)現(xiàn)原理,然而,計(jì)劃趕不上變化,突然就想先研究一下yii2.0的數(shù)據(jù)庫組件創(chuàng)建的過程。通過對(duì)yii源碼的學(xué)習(xí),了解了yii組件注冊(cè)與創(chuàng)建的過程,并發(fā)現(xiàn)原來yii組件注冊(cè)之后并不是馬上就去創(chuàng)建的,而是待到實(shí)際需要使用某個(gè)組件的時(shí)候再去創(chuàng)建對(duì)應(yīng)的組件實(shí)例的。本文大概記錄一下這個(gè)探索的過程。
要了解yii組件的注冊(cè)與創(chuàng)建,當(dāng)然要從yii入口文件index.php說起了,整個(gè)文件代碼如下:
<?php defined('YII_DEBUG') or define('YII_DEBUG', true); defined('YII_ENV') or define('YII_ENV', 'dev'); require(__DIR__ . '/../../vendor/autoload.php'); require(__DIR__ . '/../../vendor/yiisoft/yii2/Yii.php'); require(__DIR__ . '/../../common/config/bootstrap.php'); require(__DIR__ . '/../config/bootstrap.php'); $config = yii\helpers\ArrayHelper::merge( 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();
可以看到入口文件引入了幾個(gè)配置文件,并將所有配置文件的內(nèi)容都合并到$config這個(gè)配置數(shù)組中,然后使用這個(gè)配置數(shù)組作為參數(shù)去創(chuàng)建一個(gè)應(yīng)用實(shí)例。若將這個(gè)配置數(shù)組打印出來,就會(huì)看到,“components”下標(biāo)對(duì)應(yīng)的元素包含了yii組件的參數(shù)信息(這里只截圖一小部分):
這些組件的信息是在引入進(jìn)來的幾個(gè)配置文件中配置的,Yii組件就是使用這些參數(shù)信息進(jìn)行注冊(cè)與創(chuàng)建的。
接下來就進(jìn)入yii\web\Application類的實(shí)例化過程了,yii\web\Application類沒有構(gòu)造函數(shù),但是它繼承了\yii\base\Application類:
所以會(huì)自動(dòng)執(zhí)行\(zhòng)yii\base\Application類的構(gòu)造函數(shù):
public function __construct($config = []) { Yii::$app = $this; static::setInstance($this); $this->state = self::STATE_BEGIN; $this->preInit($config); $this->registerErrorHandler($config); Component::__construct($config); }
這里要順便說一下預(yù)初始化方法preInit(),它的代碼如下:
public function preInit(&$config) { /* 此處省略對(duì)$config數(shù)組的預(yù)處理操作代碼 */ // 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']; } } }
這個(gè)函數(shù)對(duì)傳遞給構(gòu)造函數(shù)的配置數(shù)組$config進(jìn)行了一些預(yù)處理操作(這里省略了),最后使用coreComponents()方法返回的數(shù)組對(duì)$config數(shù)組進(jìn)行了完善,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'], ]; }
其實(shí)就是一些核心組件的配置,也就是說這些組件是可以不需要我們?cè)谂渲梦募信渲玫?,yii會(huì)自動(dòng)進(jìn)行注冊(cè)。
好了,回到\yii\base\Application類的構(gòu)造函數(shù),這個(gè)函數(shù)最后調(diào)用了\yii\base\Component類的構(gòu)造函數(shù),但\yii\base\Component類是沒有構(gòu)造函數(shù)的,不過它繼承了\yii\base\Object類:
所以也自動(dòng)執(zhí)行了\yii\base\Object類的構(gòu)造函數(shù):
public function __construct($config = []) { if (!empty($config)) { Yii::configure($this, $config); } $this->init();
}
這里主要是調(diào)用了\yii\BaseYii類的靜態(tài)方法configure():
public static function configure($object, $properties) { foreach ($properties as $name => $value) { $object->$name = $value; } return $object; }
這個(gè)方法就是循環(huán)入口文件(new yii\web\Application($config))->run();
中的$config數(shù)組(這個(gè)數(shù)組的結(jié)構(gòu)參見本文第一個(gè)截圖),以數(shù)組鍵名作為對(duì)象屬性名,對(duì)應(yīng)的鍵值作為對(duì)象屬性值進(jìn)行賦值操作。所以當(dāng)循環(huán)到組件配置參數(shù)的時(shí)候是這樣子的:$object->components = $value($value為所有組件的配置數(shù)組),也就是對(duì)$object的components屬性進(jìn)行賦值操作,那這個(gè)$object是哪個(gè)類的對(duì)象呢?回想最初調(diào)用的源頭,其實(shí)它就是入口文件中需要進(jìn)行實(shí)例化的\yii\web\Application類的對(duì)象啊。然而,這個(gè)類和它的祖先類都沒有components這個(gè)成員變量啊,不急,又要進(jìn)行一番繼承套路了,順著yii\web\Application類的繼承關(guān)系一層一層往上找可以發(fā)現(xiàn)\yii\web\Application類最終也繼承了\yii\base\Object類,\yii\base\Object類是支持屬性的,所以yii\web\Application類也支持屬性(關(guān)于屬性,可以參考我的另一篇博文:yii2之屬性),當(dāng)賦值操作找不到components成員變量時(shí)會(huì)調(diào)用setComponents()方法,又去找這個(gè)方法的所在,終于在它的祖先類\yii\di\ServiceLocator中找到了setComponents()方法,沒錯(cuò),對(duì)應(yīng)用實(shí)例的components屬性進(jìn)行賦值操作其實(shí)就是調(diào)用這個(gè)方法!
好了,現(xiàn)在就來看看setComponents()這個(gè)方法到底干了啥:
public function setComponents($components) { foreach ($components as $id => $component) { $this->set($id, $component); } }
其實(shí)很簡單,就是循環(huán)各個(gè)組件的配置數(shù)組,調(diào)用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)); } }
其實(shí)就是把組件配置存入$_definitions這個(gè)私有成員變量(即注冊(cè)),然后呢?然后就沒有下文了。。。
搞了半天,原來yii創(chuàng)建應(yīng)用實(shí)例的時(shí)候只是進(jìn)行組件的注冊(cè),并沒有實(shí)際創(chuàng)建組件,那么組件實(shí)例是什么時(shí)候進(jìn)行創(chuàng)建的?在哪里進(jìn)行創(chuàng)建的呢?別急。從上面推導(dǎo)的這個(gè)過程我們知道\yii\di\ServiceLocator類是\yii\web\Application類的祖先類,所以其實(shí)yii的應(yīng)用實(shí)例其實(shí)就是一個(gè)服務(wù)定位器,比如我們想訪問數(shù)據(jù)庫組件的時(shí)候,我們可以這樣來訪問:Yii::$app->db,這個(gè)Yii::$app就是yii應(yīng)用實(shí)例,也就是\yii\web\Application類的實(shí)例,但是\yii\web\Application類和它的父類、祖先類都找不到db這個(gè)屬性啊。哈哈,別忘了,php讀取不到類屬性的時(shí)候會(huì)調(diào)用魔術(shù)方法__get(),所以開始查找\yii\web\Application繼承關(guān)系最近的祖先類中的__get()方法,最后在\yii\di\ServiceLocator類中找到了,也就是說,Yii::$app->db最終會(huì)調(diào)用\yii\di\ServiceLocator類中的__get()方法:
public function __get($name) { if ($this->has($name)) { return $this->get($name); } else { return parent::__get($name); } }
__get()方法首先調(diào)用has()方法(這個(gè)不再貼代碼了)判斷組件是否已注冊(cè),若已注冊(cè)則調(diào)用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是存儲(chǔ)已經(jīng)創(chuàng)建的組件實(shí)例的,若發(fā)現(xiàn)組件已經(jīng)創(chuàng)建過則直接返回組件示例,否則使用$_definitions中對(duì)應(yīng)組件的注冊(cè)信息,調(diào)用\yii\BaseYii::createObject()方法進(jìn)行組件創(chuàng)建,這個(gè)方法最終會(huì)調(diào)用依賴注入容器\yii\di\Container的get()方法,接著就是依賴注入創(chuàng)建對(duì)象的過程了,關(guān)于這個(gè)過程已經(jīng)在我的上一篇博文中講解過了,可以參考一下:yii2之依賴注入與依賴注入容器。
好了,yii組件注冊(cè)與創(chuàng)建的整個(gè)過程就是這樣的。最后總結(jié)一下,其實(shí)yii創(chuàng)建應(yīng)用實(shí)例的時(shí)候只是進(jìn)行了各個(gè)組件的注冊(cè),也就是將組件的配置信息存入\yii\di\ServiceLocator類的私有成員變量$_definitions中,并沒有進(jìn)行實(shí)際創(chuàng)建,等到程序運(yùn)行過程中真正需要使用到某個(gè)組件的時(shí)候才根據(jù)該組件在$_definitions中保存的注冊(cè)信息使用依賴注入容器\yii\di\Container進(jìn)行組件實(shí)例的創(chuàng)建,然后把創(chuàng)建的實(shí)例存入私有成員變量$_components,這樣下次訪問相同組件的時(shí)候就可以直接返回組件實(shí)例,而不再需要執(zhí)行創(chuàng)建過程了。yii的這個(gè)組件注冊(cè)與創(chuàng)建機(jī)制其實(shí)是大有裨益的,試想一下,如果在應(yīng)用實(shí)例創(chuàng)建的時(shí)候就進(jìn)行所有組件的創(chuàng)建,將會(huì)大大增加應(yīng)用實(shí)例創(chuàng)建的時(shí)間,用戶每次刷新頁面都會(huì)進(jìn)行應(yīng)用實(shí)例的創(chuàng)建的,也就是說用戶每刷新一次頁面都很慢,這用戶體驗(yàn)就很不好了,而且很多情況下有很多組件其實(shí)是沒有使用到的,但是我們還是花了不少時(shí)間去創(chuàng)建這些組件,這是很不明智的,所以yii的做法就是:先把組件參數(shù)信息保存起來,需要使用到哪些組件再去創(chuàng)建相應(yīng)的實(shí)例,大大節(jié)省了應(yīng)用創(chuàng)建的時(shí)間,同時(shí)也節(jié)省了內(nèi)存,這種思路是很值得我們學(xué)習(xí)的!
總結(jié)
以上所述是小編給大家介紹的Yii2中組件的注冊(cè)與創(chuàng)建方法,希望對(duì)大家有所幫助,如果大家有任何疑問請(qǐng)給我留言,小編會(huì)及時(shí)回復(fù)大家的。在此也非常感謝大家對(duì)腳本之家網(wǎng)站的支持!
相關(guān)文章
php 搜索框提示(自動(dòng)完成)實(shí)例代碼
輸入要搜索的文字時(shí)在搜索框下方提示相關(guān)的搜索信息實(shí)現(xiàn)方法,就是自動(dòng)完成效果2012-02-02laravel框架語言包拓展實(shí)現(xiàn)方法分析
這篇文章主要介紹了laravel框架語言包拓展實(shí)現(xiàn)方法,結(jié)合實(shí)例形式分析了laravel語言包的具體配置與使用方法,需要的朋友可以參考下2019-11-11PHP中對(duì)于浮點(diǎn)型的數(shù)據(jù)需要用不同的方法解決
這篇文章主要介紹了PHP中對(duì)于浮點(diǎn)型的數(shù)據(jù)需要用不同的方法解決。需要的朋友可以過來參考下,希望對(duì)大家有所幫助2014-03-03php實(shí)現(xiàn)微信掃碼自動(dòng)登陸與注冊(cè)功能
這篇文章主要介紹了php實(shí)現(xiàn)微信掃碼自動(dòng)登陸與注冊(cè)功能,結(jié)合實(shí)例形式分析了php微信二維碼識(shí)別接口與相關(guān)使用技巧,需要的朋友可以參考下2016-09-09Laravel 5.2 文檔 數(shù)據(jù)庫 —— 起步介紹
今天小編就為大家分享一篇Laravel 5.2 文檔 數(shù)據(jù)庫 —— 起步介紹,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2019-10-10徹底刪除thinkphp3.1案例blog標(biāo)簽的方法
這篇文章主要介紹了徹底刪除thinkphp3.1案例blog標(biāo)簽的方法,以thinkphp3.1框架中的案例blog為基礎(chǔ)介紹了刪除日記時(shí)同步刪除tag冗余數(shù)據(jù)的方法,非常具有實(shí)用價(jià)值,需要的朋友可以參考下2014-12-12PHP內(nèi)置函數(shù)生成隨機(jī)數(shù)實(shí)例
在本篇文章里小編給大家分享了關(guān)于PHP內(nèi)置函數(shù)生成隨機(jī)數(shù)實(shí)例內(nèi)容,對(duì)此有興趣的朋友們可以學(xué)習(xí)下。2019-01-01Laravel5.5中利用Passport實(shí)現(xiàn)Auth認(rèn)證的方法
Laravel5.3 開始使用Passport作為API授權(quán),Passport 是基于 OAuth2 的,下面這篇文章主要給大家介紹了關(guān)于Laravel5.5中利用Passport實(shí)現(xiàn)Auth認(rèn)證的方法,文中通過示例代碼介紹介紹的非常詳細(xì),需要的朋友可以參考借鑒,下面來一起看看吧。2017-12-12