今天本來打算研究一下yii2.0的AR模型的實現(xiàn)原理,然而,計劃趕不上變化,突然就想先研究一下yii2.0的數(shù)據(jù)庫組件創(chuàng)建的過程。通過對yii源碼的學習,了解了yii組件注冊與創(chuàng)建的過程,并發(fā)現(xiàn)原來yii組件注冊之后并不是馬上就去創(chuàng)建的,而是待到實際需要使用某個組件的時候再去創(chuàng)建對應的組件實例的。本文大概記錄一下這個探索的過程。
要了解yii組件的注冊與創(chuàng)建,當然要從yii入口文件index.php說起了,整個文件代碼如下:
?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();
可以看到入口文件引入了幾個配置文件,并將所有配置文件的內容都合并到$config這個配置數(shù)組中,然后使用這個配置數(shù)組作為參數(shù)去創(chuàng)建一個應用實例。若將這個配置數(shù)組打印出來,就會看到,“components”下標對應的元素包含了yii組件的參數(shù)信息(這里只截圖一小部分):
這些組件的信息是在引入進來的幾個配置文件中配置的,Yii組件就是使用這些參數(shù)信息進行注冊與創(chuàng)建的。
接下來就進入yii\web\Application類的實例化過程了,yii\web\Application類沒有構造函數(shù),但是它繼承了\yii\base\Application類:
所以會自動執(zhí)行\(zhòng)yii\base\Application類的構造函數(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);
}
這里要順便說一下預初始化方法preInit(),它的代碼如下:
public function preInit($config)
{
/* 此處省略對$config數(shù)組的預處理操作代碼 */
// 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'];
}
}
}
這個函數(shù)對傳遞給構造函數(shù)的配置數(shù)組$config進行了一些預處理操作(這里省略了),最后使用coreComponents()方法返回的數(shù)組對$config數(shù)組進行了完善,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類的構造函數(shù),這個函數(shù)最后調用了\yii\base\Component類的構造函數(shù),但\yii\base\Component類是沒有構造函數(shù)的,不過它繼承了\yii\base\Object類:
所以也自動執(zhí)行了\yii\base\Object類的構造函數(shù):
public function __construct($config = [])
{
if (!empty($config)) {
Yii::configure($this, $config);
}
$this->init();
}
這里主要是調用了\yii\BaseYii類的靜態(tài)方法configure():
public static function configure($object, $properties)
{
foreach ($properties as $name => $value) {
$object->$name = $value;
}
return $object;
}
這個方法就是循環(huán)入口文件(new yii\web\Application($config))->run();
中的$config數(shù)組(這個數(shù)組的結構參見本文第一個截圖),以數(shù)組鍵名作為對象屬性名,對應的鍵值作為對象屬性值進行賦值操作。所以當循環(huán)到組件配置參數(shù)的時候是這樣子的:$object->components = $value($value為所有組件的配置數(shù)組),也就是對$object的components屬性進行賦值操作,那這個$object是哪個類的對象呢?回想最初調用的源頭,其實它就是入口文件中需要進行實例化的\yii\web\Application類的對象啊。然而,這個類和它的祖先類都沒有components這個成員變量啊,不急,又要進行一番繼承套路了,順著yii\web\Application類的繼承關系一層一層往上找可以發(fā)現(xiàn)\yii\web\Application類最終也繼承了\yii\base\Object類,\yii\base\Object類是支持屬性的,所以yii\web\Application類也支持屬性(關于屬性,可以參考我的另一篇博文:yii2之屬性),當賦值操作找不到components成員變量時會調用setComponents()方法,又去找這個方法的所在,終于在它的祖先類\yii\di\ServiceLocator中找到了setComponents()方法,沒錯,對應用實例的components屬性進行賦值操作其實就是調用這個方法!
好了,現(xiàn)在就來看看setComponents()這個方法到底干了啥:
public function setComponents($components)
{
foreach ($components as $id => $component) {
$this->set($id, $component);
}
}
其實很簡單,就是循環(huán)各個組件的配置數(shù)組,調用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創(chuàng)建應用實例的時候只是進行組件的注冊,并沒有實際創(chuàng)建組件,那么組件實例是什么時候進行創(chuàng)建的?在哪里進行創(chuàng)建的呢?別急。從上面推導的這個過程我們知道\yii\di\ServiceLocator類是\yii\web\Application類的祖先類,所以其實yii的應用實例其實就是一個服務定位器,比如我們想訪問數(shù)據(jù)庫組件的時候,我們可以這樣來訪問: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是存儲已經(jīng)創(chuàng)建的組件實例的,若發(fā)現(xiàn)組件已經(jīng)創(chuàng)建過則直接返回組件示例,否則使用$_definitions中對應組件的注冊信息,調用\yii\BaseYii::createObject()方法進行組件創(chuàng)建,這個方法最終會調用依賴注入容器\yii\di\Container的get()方法,接著就是依賴注入創(chuàng)建對象的過程了,關于這個過程已經(jīng)在我的上一篇博文中講解過了,可以參考一下:yii2之依賴注入與依賴注入容器。
好了,yii組件注冊與創(chuàng)建的整個過程就是這樣的。最后總結一下,其實yii創(chuàng)建應用實例的時候只是進行了各個組件的注冊,也就是將組件的配置信息存入\yii\di\ServiceLocator類的私有成員變量$_definitions中,并沒有進行實際創(chuàng)建,等到程序運行過程中真正需要使用到某個組件的時候才根據(jù)該組件在$_definitions中保存的注冊信息使用依賴注入容器\yii\di\Container進行組件實例的創(chuàng)建,然后把創(chuàng)建的實例存入私有成員變量$_components,這樣下次訪問相同組件的時候就可以直接返回組件實例,而不再需要執(zhí)行創(chuàng)建過程了。yii的這個組件注冊與創(chuàng)建機制其實是大有裨益的,試想一下,如果在應用實例創(chuàng)建的時候就進行所有組件的創(chuàng)建,將會大大增加應用實例創(chuàng)建的時間,用戶每次刷新頁面都會進行應用實例的創(chuàng)建的,也就是說用戶每刷新一次頁面都很慢,這用戶體驗就很不好了,而且很多情況下有很多組件其實是沒有使用到的,但是我們還是花了不少時間去創(chuàng)建這些組件,這是很不明智的,所以yii的做法就是:先把組件參數(shù)信息保存起來,需要使用到哪些組件再去創(chuàng)建相應的實例,大大節(jié)省了應用創(chuàng)建的時間,同時也節(jié)省了內存,這種思路是很值得我們學習的!
總結
以上所述是小編給大家介紹的Yii2中組件的注冊與創(chuàng)建方法,希望對大家有所幫助,如果大家有任何疑問請給我留言,小編會及時回復大家的。在此也非常感謝大家對腳本之家網(wǎng)站的支持!
您可能感興趣的文章:- Yii框架學習筆記之應用組件操作示例
- yii2高級應用之自定義組件實現(xiàn)全局使用圖片上傳功能的方法
- Yii框架組件和事件行為管理詳解
- Yii擴展組件編寫方法實例分析
- yii2行為的方法如何注入到組件類中詳解
- Yii框架響應組件用法實例分析
- Yii框架核心組件類實例詳解
- PHP的Yii框架中移除組件所綁定的行為的方法
- Yii框架自定義數(shù)據(jù)庫操作組件示例
- Yii框架組件的事件機制原理與用法分析
- Yii框架應用組件用法實例分析