Yii2中组件的注册和创建方法
今天本来要研究yii2.0的AR模型的实现原理,但是计划赶不上变化,突然想先研究一下yii2.0的数据库组件的创建过程。通过对yii源代码的研究,我们了解了yii组件的注册和创建过程,发现原来的yii组件并不是注册后立即创建的,而是在实际需要某个组件时才创建相应的组件实例。这篇文章记录了这一探索的过程。
为了理解yii组件的注册和创建,我们当然应该从yii条目文件index.php开始。整个文件代码如下:
?phpdefined('YII_DEBUG ')或define('YII_DEBUG ',true);已定义(' YII _环境')或已定义(' YII _环境','发展');需要(__DIR__。'/././vendor/autoload . PHP ');需要(__DIR__。'/././vendor/yiisoft/yii 2/yii . PHP’);需要(__DIR__。'/././common/config/bootstrap . PHP ');需要(__DIR__。'/./config/bootstrap . PHP ');$ config=yii \ helper \ arrayhelper :3360 merge(require(_ _ DIR _ _)。'/././common/config/main.php '),要求(__DIR__。'/././common/config/main-local.php '),要求(__DIR__。'/./config/main.php '),要求(__DIR__。'/./config/main-local . PHP ');(新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 : settings($ this);$ this-state=self :3360 state _ BEGIN;$ this-PreInit($ config);$ this-registerErrorHandler($ config);component : _ _ construct($ config);}顺便说一下,这里是preInitialization方法preinit(),其代码如下:
public function preinit($config){/*这里省略了$ config数组的预处理操作代码*///将核心组件与自定义组件foreach ($ this-core components()合并为$ id=$ component) {if(!isset($ config[' components '][$ id]){ $ config[' components '][$ id]=$ component;} else if(is _ array($ config[' components '][$ id])!isset($ config[' components '][$ id][' class ']){ $ config[' components '][$ id][' class ']=$ component[' class '];}}}该函数对传递给构造函数的配置数组$config进行预处理(此处省略),最后用coreComponents()方法返回的数组完善$config数组,如下所示:
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会自动注册。
好,回到\yii\base\Application类的构造函数,这个函数最后调用\yii\base\Component类的构造函数,但是\yii\base\Component类没有构造函数,但是继承了\yii\base\Object类:
因此,自动执行\yii\base\Object类的构造函数:
public function _ _ construct($ config=[]){ if(!空($ config)){ yii :3360 configure($ this,$ config);} $ this-init();}这里调用了\yii\BaseYii类的静态方法configure():
公共静态函数configure($object,$ properties){ foreach($ properties as $ name=$ value){ $ object-$ name=$ value;}返回$ object}此方法是循环入口文件(new yii \ web \ application($ config))-run();中的$config数组(有关该数组的结构,请参见本文的第一个屏幕截图),数组键名作为对象属性名,对应的键值作为对象属性值。因此,当循环到组件配置参数时,看起来是这样的:$object-components=$ value($ value是所有组件的配置数组),即$ object的components属性被赋值。这个$ object是什么样的对象?回顾初始调用的来源,它实际上是需要在门户文件中实例化的\yii\web\Application类的对象。然而,这个类和它的祖先类都没有组件作为成员变量。不急,有必要再进行一些继承套路。沿着yii\web\Application类的继承关系逐层查找,可以发现\yii\web\Application类最终继承\ yii \ base \ Object类支持属性,所以yii\web\Application类也支持属性(属性请参考我的另一篇博文:yii2的属性)。当赋值操作找不到components成员变量时,它会调用setComponents()方法,然后找出这个方法在哪里,最后在其祖先类\yii\di\ SetComponents()方法中找到了ServiceLocator。是的,给应用程序实例的components属性赋值实际上是在调用这个方法!
好了,现在让我们看看setComponents()方法做了什么:
公共函数setComponents($ components){ foreach($ components as $ id=$ component){ $ this-set($ id,$ component);}}其实很简单,就是循环每个组件的配置数组,调用set()方法,如下所示:
公共函数集($id,$ definition){ unset($ this-_ components[$ id]);if($ definition===null){ unset($ this-_ definitions[$ id]);返回;} if(is _ object($ definition)| | is _ callable($ definition,true)) { //一个对象、一个类名或一个PHP可调用的$ this-_ definitions[$ id]=$ definition;} else if(is _ array($ definition)){//一个配置数组if(isset($ definition[' class ']){ $ this-_ definitions[$ id]=$ definition;} else {抛出新的InvalidConfigException(' \ ' $ id '组件的配置必须包含一个' class '元素);} } else {抛出新的InvalidConfigException(\ ' $ id ' component :的意外配置类型)。gettype($ definition));}}实际上是将组件配置存储在私有成员变量$_definitions(即register)中,然后呢?那就没有下文了。
很长一段时间后,yii在创建应用实例时,只注册了组件,实际上并没有创建组件。那么组件实例是什么时候创建的呢?它是在哪里创造的?别担心。从上面推导的过程中,我们知道\yii\di\ServiceLocator类是\yii\web\Application类的祖先类,所以yii的应用实例实际上是一个服务定位器。例如,当我们想要访问数据库组件时,我们可以这样访问它:Yii:$app-db-DB。Yii:$app是Yii应用程序实例,即\yii\web\Application类的实例,但\yii\web\Application类及其父类和祖先类找不到属性db。哈哈,别忘了,php在无法读取类属性的时候会调用magic method __get(),于是开始在\yii\web\Application的祖先类中寻找继承关系最接近的__get()方法,最后在\yii\di\ServiceLocator类中找到,也就是yii3 $ app
public function _ _ get($ name){ if($ this-has($ name)){ return $ this-get($ name);} else { return parent : _ _ get($ name);}}__get()方法首先调用has()方法(此处不再编码)判断组件是否已注册,如果已注册,则调用get()方法:
public function get($id,$ throw exception=true){ if(isset($ this-_ components[$ id]){ return $ this-_ components[$ id];} if(isset($ this-_ definitions[$ id]){ $ definition=$ this-_ definitions[$ id];if (is_object($definition)!$ definition instance of Closure){ return $ this-_ components[$ id]=$ definition;} else { return $ this-_ components[$ id]=yii 3360: createobject($ definition);} } else if($ throw exception){ throw new InvalidConfigException('未知组件id : $ id ');} else { return null}}其中私有成员变量$_components存储创建的组件实例,如果发现组件已经创建,将直接返回组件实例;否则,使用$_definitions中对应组件的注册信息,调用\ yii \ baseyii :3360 createobject()方法创建组件,最终将调用依赖注入容器\ yii \ di \ Container的get()方法后面是依赖注入创建对象的过程。这个过程已经在我上一篇博文中解释过了。请参考yii2的依赖注入和依赖注入容器。
好的,yii组件注册和创建的整个过程是这样的。最后总结一下,其实yii创建应用实例的时候,只注册了每个组件,也就是把组件的配置信息存储在\yii\di\ServiceLocator类的私有成员变量$_definitions中,并没有实际创建。当程序运行过程中确实需要某个组件时,根据$_definitions中存储的注册信息,使用依赖注入容器\yii\di\Container创建组件实例,然后将创建的实例存储在私有成员变量$_components中,这样下次访问同一个组件时就可以直接返回组件实例,不再需要创建过程。yii的这个组件注册和创建机制实际上是有益的。想象一下,如果在创建应用程序实例时创建所有组件,将大大增加创建应用程序实例的时间。每次用户刷新页面时,都会创建应用程序实例。也就是说,用户每次刷新页面都很慢,这让用户体验非常不好,很多情况下很多组件实际上是没有使用的。然而,我们仍然花费大量时间来创建这些组件,这是不明智的。所以yii的做法是先保存组件参数信息,在创建对应的实例之前需要用到哪些组件,这样就大大节省了应用创建的时间和内存。这个想法值得学习!
摘要
以上是边肖介绍的Yii2中组件的注册和创建方法。希望对大家有帮助。如果你有任何问题,请给我留言,边肖会及时回复你。非常感谢您对我们网站的支持!
版权声明:Yii2中组件的注册和创建方法是由宝哥软件园云端程序自动收集整理而来。如果本文侵犯了你的权益,请联系本站底部QQ或者邮箱删除。

















