搜索
首页后端开发php教程Yii2中关于组件的注册以及创建的方法详解

 今天本来打算研究一下yii2.0的AR模型的实现原理,然而,计划赶不上变化,突然就想先研究一下yii2.0的数据库组件创建的过程。通过对yii源码的学习,了解了yii组件注册与创建的过程,并发现原来yii组件注册之后并不是马上就去创建的,而是待到实际需要使用某个组件的时候再去创建对应的组件实例的。本文大概记录一下这个探索的过程。

  要了解yii组件的注册与创建,当然要从yii入口文件index.php说起了,整个文件代码如下:

<?php
defined(&#39;YII_DEBUG&#39;) or define(&#39;YII_DEBUG&#39;, true);
defined(&#39;YII_ENV&#39;) or define(&#39;YII_ENV&#39;, &#39;dev&#39;);
require(DIR . &#39;/../../vendor/autoload.php&#39;);
require(DIR . &#39;/../../vendor/yiisoft/yii2/Yii.php&#39;);
require(DIR . &#39;/../../common/config/bootstrap.php&#39;);
require(DIR . &#39;/../config/bootstrap.php&#39;);
$config = yii\helpers\ArrayHelper::merge(
 require(DIR . &#39;/../../common/config/main.php&#39;),
 require(DIR . &#39;/../../common/config/main-local.php&#39;),
 require(DIR . &#39;/../config/main.php&#39;),
 require(DIR . &#39;/../config/main-local.php&#39;)
);
(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[&#39;components&#39;][$id])) {
   $config[&#39;components&#39;][$id] = $component;
  } elseif (is_array($config[&#39;components&#39;][$id]) && !isset($config[&#39;components&#39;][$id][&#39;class&#39;])) {
   $config[&#39;components&#39;][$id][&#39;class&#39;] = $component[&#39;class&#39;];
  }
 }
}

  这个函数对传递给构造函数的配置数组$config进行了一些预处理操作(这里省略了),最后使用coreComponents()方法返回的数组对$config数组进行了完善,coreComponents()方法是这样的:

public function coreComponents()
{
 return [
  &#39;log&#39; => [&#39;class&#39; => &#39;yii\log\Dispatcher&#39;],
  &#39;view&#39; => [&#39;class&#39; => &#39;yii\web\View&#39;],
  &#39;formatter&#39; => [&#39;class&#39; => &#39;yii\i18n\Formatter&#39;],
  &#39;i18n&#39; => [&#39;class&#39; => &#39;yii\i18n\I18N&#39;],
  &#39;mailer&#39; => [&#39;class&#39; => &#39;yii\swiftmailer\Mailer&#39;],
  &#39;urlManager&#39; => [&#39;class&#39; => &#39;yii\web\UrlManager&#39;],
  &#39;assetManager&#39; => [&#39;class&#39; => &#39;yii\web\AssetManager&#39;],
  &#39;security&#39; => [&#39;class&#39; => &#39;yii\base\Security&#39;],
 ];
}

  其实就是一些核心组件的配置,也就是说这些组件是可以不需要我们在配置文件中配置的,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[&#39;class&#39;])) {
   $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中文网其他相关文章!

声明
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn
您如何修改PHP会话中存储的数据?您如何修改PHP会话中存储的数据?Apr 27, 2025 am 12:23 AM

tomodifyDataNaphPsession,startTheSessionWithSession_start(),然后使用$ _sessionToset,修改,orremovevariables.1)startThesession.2)setthesession.2)使用$ _session.3)setormodifysessessvariables.3)emovervariableswithunset()

举一个在PHP会话中存储数组的示例。举一个在PHP会话中存储数组的示例。Apr 27, 2025 am 12:20 AM

在PHP会话中可以存储数组。1.启动会话,使用session_start()。2.创建数组并存储在$_SESSION中。3.通过$_SESSION检索数组。4.优化会话数据以提升性能。

垃圾收集如何用于PHP会议?垃圾收集如何用于PHP会议?Apr 27, 2025 am 12:19 AM

PHP会话垃圾回收通过概率机制触发,清理过期会话数据。1)配置文件中设置触发概率和会话生命周期;2)可使用cron任务优化高负载应用;3)需平衡垃圾回收频率与性能,避免数据丢失。

如何在PHP中跟踪会话活动?如何在PHP中跟踪会话活动?Apr 27, 2025 am 12:10 AM

PHP中追踪用户会话活动通过会话管理实现。1)使用session_start()启动会话。2)通过$_SESSION数组存储和访问数据。3)调用session_destroy()结束会话。会话追踪用于用户行为分析、安全监控和性能优化。

如何使用数据库存储PHP会话数据?如何使用数据库存储PHP会话数据?Apr 27, 2025 am 12:02 AM

利用数据库存储PHP会话数据可以提高性能和可扩展性。1)配置MySQL存储会话数据:在php.ini或PHP代码中设置会话处理器。2)实现自定义会话处理器:定义open、close、read、write等函数与数据库交互。3)优化和最佳实践:使用索引、缓存、数据压缩和分布式存储来提升性能。

简单地说明PHP会话的概念。简单地说明PHP会话的概念。Apr 26, 2025 am 12:09 AM

phpsessionstrackuserdataacrossmultiplepagerequestsusingauniqueIdStoredInacookie.here'showtomanageThemeffectionaly:1)startAsessionWithSessionwwithSession_start()和stordoredAtain $ _session.2)

您如何循环中存储在PHP会话中的所有值?您如何循环中存储在PHP会话中的所有值?Apr 26, 2025 am 12:06 AM

在PHP中,遍历会话数据可以通过以下步骤实现:1.使用session_start()启动会话。2.通过foreach循环遍历$_SESSION数组中的所有键值对。3.处理复杂数据结构时,使用is_array()或is_object()函数,并用print_r()输出详细信息。4.优化遍历时,可采用分页处理,避免一次性处理大量数据。这将帮助你在实际项目中更有效地管理和使用PHP会话数据。

说明如何使用会话进行用户身份验证。说明如何使用会话进行用户身份验证。Apr 26, 2025 am 12:04 AM

会话通过服务器端的状态管理机制实现用户认证。1)会话创建并生成唯一ID,2)ID通过cookies传递,3)服务器存储并通过ID访问会话数据,4)实现用户认证和状态管理,提升应用安全性和用户体验。

See all articles

热AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智能驱动的应用程序,用于创建逼真的裸体照片

AI Clothes Remover

AI Clothes Remover

用于从照片中去除衣服的在线人工智能工具。

Undress AI Tool

Undress AI Tool

免费脱衣服图片

Clothoff.io

Clothoff.io

AI脱衣机

Video Face Swap

Video Face Swap

使用我们完全免费的人工智能换脸工具轻松在任何视频中换脸!

热工具

Dreamweaver CS6

Dreamweaver CS6

视觉化网页开发工具

SublimeText3 英文版

SublimeText3 英文版

推荐:为Win版本,支持代码提示!

mPDF

mPDF

mPDF是一个PHP库,可以从UTF-8编码的HTML生成PDF文件。原作者Ian Back编写mPDF以从他的网站上“即时”输出PDF文件,并处理不同的语言。与原始脚本如HTML2FPDF相比,它的速度较慢,并且在使用Unicode字体时生成的文件较大,但支持CSS样式等,并进行了大量增强。支持几乎所有语言,包括RTL(阿拉伯语和希伯来语)和CJK(中日韩)。支持嵌套的块级元素(如P、DIV),

EditPlus 中文破解版

EditPlus 中文破解版

体积小,语法高亮,不支持代码提示功能

适用于 Eclipse 的 SAP NetWeaver 服务器适配器

适用于 Eclipse 的 SAP NetWeaver 服务器适配器

将Eclipse与SAP NetWeaver应用服务器集成。