Maison  >  Article  >  développement back-end  >  Explication détaillée des méthodes d'enregistrement et de création de composants dans Yii2

Explication détaillée des méthodes d'enregistrement et de création de composants dans Yii2

黄舟
黄舟original
2017-11-01 09:24:311655parcourir

Aujourd'hui, j'avais initialement prévu d'étudier le principe de mise en œuvre du modèle AR de yii2.0. Cependant, le plan n'a pas pu suivre les changements, et du coup j'ai voulu étudier le processus de création du composant base de données de yii2.0. d'abord. En étudiant le code source de yii, j'ai découvert le processus d'enregistrement et de création de composants yii, et j'ai découvert que les composants yii d'origine ne sont pas créés immédiatement après l'enregistrement, mais que les composants correspondants sont créés lorsqu'un certain composant est réellement nécessaire. Cet article enregistre grossièrement ce processus d'exploration.

Pour comprendre l'enregistrement et la création des composants yii, il faut bien sûr commencer par le fichier d'entrée yiiindex.php. Le code complet du fichier est le suivant :

.
<?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();

Vous pouvez voir que le fichier d'entrée introduit plusieurs fichiers de configuration, fusionne le contenu de tous les fichiers de configuration dans le tableau de configuration $config, puis utilise ce tableau de configuration comme paramètre pour créer une instance d'application. Si vous imprimez ce tableau de configuration, vous verrez que l'élément correspondant à l'indice "components" contient les informations sur les paramètres du composant yii (seule une petite partie de la capture d'écran est prise ici) :

Les informations de ces composants sont configurées dans plusieurs fichiers de configuration importés. Les composants Yii sont enregistrés et créés à l'aide de ces informations de paramètres.

L'étape suivante consiste à entrer dans le processus d'instanciation de la classe yiiwebApplication. La classe yiiwebApplication n'a pas de constructeur , mais elle hérite de la classe yiibaseApplication :

donc le La classe yiibaseApplication sera automatiquement exécutée. Constructeur :

public function construct($config = [])
{
 Yii::$app = $this;
 static::setInstance($this);
 $this->state = self::STATE_BEGIN;
 $this->preInit($config);
 $this->registerErrorHandler($config);
 Component::construct($config);
}

Au fait, voici la méthode de pré-initialisation preInit(), son code est le suivant :

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;];
  }
 }
}

Cette fonction. gère le tableau de configuration $config passé au constructeur Après quelques opérations de prétraitement (omises ici), le tableau $config a finalement été amélioré en utilisant le tableau renvoyé par la méthode coreComponents(). La méthode coreComponents() est comme ceci :

<.>
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;],
 ];
}
En fait, il s'agit d'une configuration de composants de base, c'est-à-dire que ces composants n'ont pas besoin d'être configurés dans le fichier de configuration, Yii les enregistrera automatiquement.

Ok, revenons au constructeur de la classe yiibaseApplication, cette fonction appelle enfin le constructeur de la classe yiibaseComponent, mais la classe yiibaseComponent n'a pas de constructeur, mais elle hérite de la classe yiibaseObject :

Par conséquent, le constructeur de la classe yiibaseObject est également exécuté automatiquement :

public function construct($config = [])
{
 if (!empty($config)) {
  Yii::configure($this, $config);
 }
 $this->init();
}
Ici appelle principalement la méthode statique configure() de la classe yiiBaseYii :

public static function configure($object, $properties)
{
 foreach ($properties as $name => $value) {
  $object->$name = $value;
 }
 return $object;
}
Cette méthode est la fichier d'entrée de boucle (voir la première capture d'écran de cet article pour la structure de ce tableau) utilise le nom de clé du tableau comme

nom d'attribut d'objet (new yiiwebApplication($config))->run();, et la valeur de clé correspondante comme valeur d'attribut d'objet pour l'affectation. Ainsi, lors d'une boucle sur les paramètres de configuration des composants, cela ressemble à ceci : $object->components = $value ($value est le tableau de configuration de tous les composants), c'est-à-dire attribuer une valeur à l'attribut composants de $object, puis this $object De quelle classe d'objet s'agit-il ? En revenant à la source de l'appel initial, c'est en fait l'objet de la classe yiiwebApplication qui doit être instancié dans le fichier d'entrée. Cependant, ni cette classe ni sa classe ancêtre n'ont les composants de variable membre. Ne vous inquiétez pas, nous devons refaire quelques routines d'héritage. Suivez la relation d'héritage de la classe yiiwebApplication et recherchez couche par couche. la classe finit par hériter également. La classe yiibaseObject prend en charge les attributs, donc la classe yiiwebApplication prend également en charge les attributs (pour les attributs, vous pouvez vous référer à mon autre article de blog : Attributs de yii2). Lorsque l'opération d'affectation ne trouve pas la variable membre des composants, setComponents() le fera. être appelée., et a recherché l'emplacement de cette méthode, et a finalement trouvé la méthode setComponents() dans sa classe ancêtre yiidiServiceLocator Oui, l'attribution de valeurs à l'attribut composants de l'instance d'application appelle en fait cette méthode ! OK, regardons maintenant ce que fait la méthode setComponents() :

C'est en fait très simple, il suffit de parcourir le tableau de configuration de chaque composant et d'appeler set( ), la méthode set() est la suivante :
public function setComponents($components)
{
 foreach ($components as $id => $component) {
  $this->set($id, $component);
 }
}

En fait, elle enregistre la configuration du composant dans la variable membre privée $_definitions (c'est-à-dire l'enregistrement), et ensuite quoi ? Ensuite, il n'y en a plus. . .
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));
 }
}

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

总结

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Déclaration:
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn