热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

Yii2中组件的注册与创建方法

这篇文章主要介绍了Yii2之组件的注册与创建的实现方法,非常不错,具有参考借鉴价值,需要的朋友可以参考下

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

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

<&#63;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中组件的注册与创建方法,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对网站的支持!


推荐阅读
  • 处理docker容器时间和宿主机时间不一致问题的方法
    本文介绍了处理docker容器时间和宿主机时间不一致问题的方法,包括复制主机的localtime到容器、处理报错情况以及重启容器的步骤。通过这些方法,可以解决docker容器时间和宿主机时间不一致的问题。 ... [详细]
  • 本文介绍了Java的集合及其实现类,包括数据结构、抽象类和具体实现类的关系,详细介绍了List接口及其实现类ArrayList的基本操作和特点。文章通过提供相关参考文档和链接,帮助读者更好地理解和使用Java的集合类。 ... [详细]
  • 本文介绍了前端人员必须知道的三个问题,即前端都做哪些事、前端都需要哪些技术,以及前端的发展阶段。初级阶段包括HTML、CSS、JavaScript和jQuery的基础知识。进阶阶段涵盖了面向对象编程、响应式设计、Ajax、HTML5等新兴技术。高级阶段包括架构基础、模块化开发、预编译和前沿规范等内容。此外,还介绍了一些后端服务,如Node.js。 ... [详细]
  • 延迟注入工具(python)的SQL脚本
    本文介绍了一个延迟注入工具(python)的SQL脚本,包括使用urllib2、time、socket、threading、requests等模块实现延迟注入的方法。该工具可以通过构造特定的URL来进行注入测试,并通过延迟时间来判断注入是否成功。 ... [详细]
  • Spring常用注解(绝对经典),全靠这份Java知识点PDF大全
    本文介绍了Spring常用注解和注入bean的注解,包括@Bean、@Autowired、@Inject等,同时提供了一个Java知识点PDF大全的资源链接。其中详细介绍了ColorFactoryBean的使用,以及@Autowired和@Inject的区别和用法。此外,还提到了@Required属性的配置和使用。 ... [详细]
  • 本文介绍了一个免费的asp.net控件,该控件具备数据显示、录入、更新、删除等功能。它比datagrid更易用、更实用,同时具备多种功能,例如属性设置、数据排序、字段类型格式化显示、密码字段支持、图像字段上传和生成缩略图等。此外,它还提供了数据验证、日期选择器、数字选择器等功能,以及防止注入攻击、非本页提交和自动分页技术等安全性和性能优化功能。最后,该控件还支持字段值合计和数据导出功能。总之,该控件功能强大且免费,适用于asp.net开发。 ... [详细]
  • 背景应用安全领域,各类攻击长久以来都危害着互联网上的应用,在web应用安全风险中,各类注入、跨站等攻击仍然占据着较前的位置。WAF(Web应用防火墙)正是为防御和阻断这类攻击而存在 ... [详细]
  • 本文介绍了互联网思维中的三个段子,涵盖了餐饮行业、淘品牌和创业企业的案例。通过这些案例,探讨了互联网思维的九大分类和十九条法则。其中包括雕爷牛腩餐厅的成功经验,三只松鼠淘品牌的包装策略以及一家创业企业的销售额增长情况。这些案例展示了互联网思维在不同领域的应用和成功之道。 ... [详细]
  • Spring框架《一》简介
    Spring框架《一》1.Spring概述1.1简介1.2Spring模板二、IOC容器和Bean1.IOC和DI简介2.三种通过类型获取bean3.给bean的属性赋值3.1依赖 ... [详细]
  • 1.脚本功能1)自动替换jar包中的配置文件。2)自动备份老版本的Jar包3)自动判断是初次启动还是更新服务2.脚本准备进入ho ... [详细]
  • 初识java关于JDK、JRE、JVM 了解一下 ... [详细]
  • 浅解XXE与Portswigger Web Sec
    XXE与PortswiggerWebSec​相关链接:​博客园​安全脉搏​FreeBuf​XML的全称为XML外部实体注入,在学习的过程中发现有回显的XXE并不多,而 ... [详细]
  • 一、Struts2是一个基于MVC设计模式的Web应用框架在MVC设计模式中,Struts2作为控制器(Controller)来建立模型与视图的数据交互。Struts2优点1、实现 ... [详细]
  • ElasticSearch成功安装完毕。 测试数据添加出现{  error:{    root_cause ... [详细]
  • 基于SpringBoot打造在线教育系统(6)– 二级分类模块UI篇
    这一节来做二级分类,为了快速开发,一级分类只做新增,暂时不考虑修改和删除,如果一定要删,就去数据库删吧。我们接下来,需要通过一级分类,获取所有的二级分类。开始 ... [详细]
author-avatar
用户cnhr0qjy0s
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有