Home > Article > Backend Development > Detailed explanation of the registration and creation methods of components in Yii2
Today I originally planned to study the implementation principle of the AR model of yii2.0. However, the plan could not keep up with the changes, and suddenly I wanted to study the creation process of the database component of yii2.0 first. Through studying the yii source code, I learned about the process of registering and creating yii components, and discovered that the yii components are not created immediately after registration, but that the corresponding components are created when a component is actually needed. Example. This article briefly records this exploration process.
To understand the registration and creation of yii components, of course we must start with the yiientry fileindex.php. The entire file code is as follows:
<?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();
Yes See that the entry file introduces several configuration files, merges the contents of all configuration files into the $config configuration array, and then uses this configuration array as a parameter to create an application instance. If you print out this configuration array, you will see that the element corresponding to the "components" subscript contains the parameter information of the yii component (only a small part of the screenshot is taken here):
The information of these components is configured in several imported configuration files. Yii components are registered and created using these parameter information.
The next step is to enter the instantiation process of the yii\web\Application class. The yii\web\Application class does not have a constructor , but it inherits the \yii\base\Application class:
So the constructor of the \yii\base\Application class will be automatically executed:
public function construct($config = []) { Yii::$app = $this; static::setInstance($this); $this->state = self::STATE_BEGIN; $this->preInit($config); $this->registerErrorHandler($config); Component::construct($config); }
By the way, here is the pre-initialization method preInit(), its code is as follows:
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']; } } }
This function performs some preprocessing operations on the configuration array $config passed to the constructor (omitted here), and finally uses the array returned by the coreComponents() method to complete the $config array. The coreComponents() method is like this :
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'], ]; }
In fact, it is the configuration of some core components, which means that these components do not need to be configured in the configuration file, Yii will automatically register them.
Okay, back to the constructor of the \yii\base\Application class. This function finally calls the constructor of the \yii\base\Component class, but the \yii\base\Component class has no constructor. Yes, but it inherits the \yii\base\Object class:
So it also automatically executes the constructor of the \yii\base\Object class:
public function construct($config = []) { if (!empty($config)) { Yii::configure($this, $config); } $this->init(); }
Here it mainly calls \yii \Static method configure() of the BaseYii class:
public static function configure($object, $properties) { foreach ($properties as $name => $value) { $object->$name = $value; } return $object; }
This method is in the loop entry file (new yii\web\Application($config))->run();
In the $config array (see the first screenshot of this article for the structure of this array), the array key name is used as the object attribute name, and the corresponding key value is used as the object attribute value for the assignment operation. So when looping to the component configuration parameters, it looks like this: $object->components = $value ($value is the configuration array of all components), that is, assigning a value to the components attribute of $object, then this $object Which class of object is it? Looking back at the source of the initial call, it is actually the object of the \yii\web\Application class that needs to be instantiated in the entry file. However, neither this class nor its ancestor class has the member variable components. Don’t worry, we have to do some inheritance routines again. You can find it by following the inheritance relationship of the yii\web\Application class and looking up layer by layer\ The yii\web\Application class finally inherits the \yii\base\Object class. The \yii\base\Object class supports attributes, so the yii\web\Application class also supports attributes (for attributes, you can refer to my other blog post: Attributes of yii2), when the assignment operation cannot find the components member variable, the setComponents() method will be called, and then the location of this method is found, and finally setComponents() is found in its ancestor class \yii\di\ServiceLocator ) method, yes, the assignment operation to the components attribute of the application instance is actually calling this method!
Okay, now let’s take a look at what the setComponents() method does:
public function setComponents($components) { foreach ($components as $id => $component) { $this->set($id, $component); } }
It’s actually very simple, just loop through the configuration array of each component, call the set() method, set () The method is as follows:
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)); } }
In fact, it is to store the component configuration in the private member variable $_definitions (i.e. registration), and then what? Then there is no more. . .
搞了半天,原来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的做法就是:先把组件参数信息保存起来,需要使用到哪些组件再去创建相应的实例,大大节省了应用创建的时间,同时也节省了内存,这种思路是很值得我们学习的!
总结
The above is the detailed content of Detailed explanation of the registration and creation methods of components in Yii2. For more information, please follow other related articles on the PHP Chinese website!