今天本來打算研究一下yii2.0的AR模型的實現原理,然而,計劃趕不上變化,突然就想先研究一下yii2.0的資料庫組件創建的過程。透過對yii原始碼的學習,了解了yii元件註冊與創建的過程,並發現原來yii元件註冊之後並不是馬上就去創建的,而是待到實際需要使用某個元件的時候再去創建對應的元件實例的。本文大概記錄這個探索的過程。
要了解yii元件的註冊與創建,當然要從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這個配置數組中,然後使用這個配置數組作為參數去創建一個應用實例。若將這個配置數組列印出來,就會看到,「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的做法就是:先把组件参数信息保存起来,需要使用到哪些组件再去创建相应的实例,大大节省了应用创建的时间,同时也节省了内存,这种思路是很值得我们学习的!
总结
以上是Yii2中關於組件的註冊以及創建的方法詳解的詳細內容。更多資訊請關注PHP中文網其他相關文章!