2019独角兽企业重金招聘Python工程师标准>>>

了解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的做法就是:先把组件参数信息保存起来,需要使用到哪些组件再去创建相应的实例,大大节省了应用创建的时间,同时也节省了内存,这种思路是很值得我们学习的!

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

转载于:https://my.oschina.net/botkenni/blog/1826977

Yii2中关于组件的注册以及创建的方法详解相关推荐

  1. python反向缩进_在Pycharm中对代码进行注释和缩进的方法详解

    在Pycharm中对代码进行注释和缩进的方法详解 一.注释 1. #单行注释 2. """ 多行注释 """ 3. pycharm多行注释快 ...

  2. python 读取图片转换为一维向量_对Python中一维向量和一维向量转置相乘的方法详解...

    对Python中一维向量和一维向量转置相乘的方法详解 在Python中有时会碰到需要一个一维列向量(n*1)与另一个一维列向量(n*1)的转置(1*n)相乘,得到一个n*n的矩阵的情况.但是在pyth ...

  3. python二维元组_python中读入二维csv格式的表格方法详解(以元组/列表形式表示)

    如何去读取一个没有表头的二维csv文件(如下图所示)? 并以元组的形式表现数据: ((1.0, 0.0, 3.0, 180.0), (2.0, 0.0, 2.0, 180.0), (3.0, 0.0, ...

  4. python可以使用二维元组吗_python中读入二维csv格式的表格方法详解(以元组/列表形式表示)...

    怎么去读取一个没有表头的二维csv文件(如下图所示)? 并以元组的形式表现数据: ((1.0, 0.0, 3.0, 180.0), (2.0, 0.0, 2.0, 180.0), (3.0, 0.0, ...

  5. python元组读取到列表_python中读入二维csv格式的表格方法详解(以元组/列表形式表示)...

    如何去读取一个没有表头的二维csv文件(如下图所示)? 并以元组的形式表现数据: ((1.0, 0.0, 3.0, 180.0), (2.0, 0.0, 2.0, 180.0), (3.0, 0.0, ...

  6. 绝地腾讯手游登入显示服务器满了,绝地求生刺激战场注册已满什么意思 注册已满解决方法详解[多图]...

    绝地求生刺激战场2月9日正式开启全平台测试,不过有许多玩家出现注册已满的情况,无法进行游戏,下面安族小编给大家介绍一下注册已满解决方法详解. 绝地求生刺激战场今日注册上限解决办法 有玩家在网上说自己早 ...

  7. python读二进制格点雷达基数据_对numpy中二进制格式的数据存储与读取方法详解...

    使用save可以实现对numpy数据的磁盘存储,存储的方式是二进制.查看使用说明,说明专门提到了是未经压缩的二进制形式.存储后的数据可以进行加载或者读取,通过使用load方法. In [81]:np. ...

  8. python中的sep参数_Python sep参数使用方法详解

    Python sep参数使用方法详解 这篇文章主要介绍了Python sep参数使用方法详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 Pyth ...

  9. mysql 不通过关联查询表_mysql中多表不关联查询的实现方法详解

    下面小编就为大家带来一篇浅谈mysql中多表不关联查询的实现方法.小编觉得挺不错的,现在就分享给大家,也给大家做个参考.一起跟随小编过来看看吧 大家在使用MySQL查询时正常是直接一个表的查询,要不然 ...

最新文章

  1. Struts2和SpringMVC的执行流程。
  2. android 动画引擎,一个使用openGL渲染的炫丽Android动画库
  3. 什么是python-马哥教育官网-专业Linux培训班,Python培训机构
  4. python用pywin32库来隐藏windows文件
  5. manual php,PHP - Manual: 介绍 (官方文档)
  6. 网络基础3-1(细谈IP协议头, 网络层,子网划分,路由选择,数据链路层,以太网帧格式,MAC地址,再谈ARP协议)
  7. Mongodb 安装和启动
  8. 树莓派利用PuTTY进行远程登录
  9. leetcode - 516. 最长回文子序列
  10. c语言4钟存储类型关键字用法,求C语言中的32个关键字及其意思? C语言中32个关键字的意思和用法~!...
  11. android仿微信图片编辑器,electron/vue可编辑框contenteditable|仿微信截图
  12. Java虚拟机类加载机制浅谈
  13. 【Spring-AOP】Spring提供的AOP开发方式和底层AOP开发方式
  14. Eclipse的使用教程
  15. Line in和mac in 的区别
  16. 春运抢火车票攻略汇总(持续更新中)
  17. 前端开发中PC端和移动端网站的区别
  18. java解析xml文件并写入Excel表
  19. 算法相关-互联网计算广告学
  20. 目前最流畅的android手机,买安卓手机请认准这五个最流畅的系统

热门文章

  1. python if语句多个条件-关于函数:如何在python中为一个if语句提供多个条件
  2. python列表按照指定顺序排序-pandas中的DataFrame按指定顺序输出所有列的方法
  3. python3.7和3.8的区别-Python 3.8 有什么新变化
  4. Windows10忘记锁屏密码,如何破解
  5. 网络编程学习笔记(TCP回射服务器程序修订版)
  6. 题目1170:找最小数
  7. nginx 访问控制之deny allow
  8. ACE-Streams架构简介及应用
  9. 1475 m进制转十进制
  10. 去除字符串标点 + 泛型算法使用