Yii 系统启动 trace源码
2016-05-10 23:20
525 查看
摘要:学习使用Yii框架,总觉得使用起来不顺手,趁这几天工作不忙,就trace下框架源码吧。这篇先来trace从入口文件到控制器启动的过程。
在这里new出了第一个对象,CWebApplication
继承关系如下:
new的时候,调用了构造函数__construct(),这个构造函数是在CApplication这个类里面声明的。
就是在这个构造函数里面实现了系统初始化工作。
下面trace 这个方法,有玄机
至于为什么在系统初始化的时候,先把核心组件的配置信息(其实就是组件类名)先写进CWepApplication的私有属性$_componentConfig里面,然后才将配置文件里面的组件配置信息更新进来。
我想原因应该是:自己在配置文件里面是不用关心系统启动需要的组件的,在框架里面先把框架启动需要的组件类先加进来,然后自己在配置文件里面的配置只要合并进来就好了。
先上代码:
乍看很普通,就是把$config里面的值,按照键值对,复制进对象里面的属性和值。
容易忽略一点是,在继承关系的最顶层是CComponent,里面有魔术方法__set(),看看这个魔术方法是什么样子(只取出关键代码):
比如在/config/main.php里面配置了组件的配置信息,像这样:
那么就相当于在configure()方法里面执行:$this->components = array();
因为类里面并没有声明components这个属性,所以对这个属性赋值的时候会调用魔术方法__set()
在魔术方法里面,先检查setcomponents()这个方法是否存在,当然是存在的,这个方法被声明在system.base.CModule里面;
所以,我们在/config/main.php里面对组件(components)进行配置的时候,那么配置项并不是简单的复制进CWepApplication的属性里面,而是调用了setComponents()方法。
在config/main.php里面的一个配置项:’preload’=>array(‘log’),
至此一个请求的初始化就完成了。
1. 入口文件index.php
$yii=dirname(__FILE__).'/../../lib/yii-1.1.16/yii.php'; require_once($yii); Yii::createWebApplication($config)->run();
2. system.YiiBase
public static function createWebApplication($config=null) { return self::createApplication('CWebApplication',$config); } public static function createApplication($class,$config=null) { return new $class($config); }
在这里new出了第一个对象,CWebApplication
继承关系如下:
class CWebApplication » CApplication » CModule » CComponent
new的时候,调用了构造函数__construct(),这个构造函数是在CApplication这个类里面声明的。
就是在这个构造函数里面实现了系统初始化工作。
3. system.web.CApplication
这里只trace部分代码(component相关)public function __construct($config=null) { //tag Yii::setApplication($this); //作者特意在这里写了注释:设置basePath要尽可能的早,以避免麻烦,估计这个路径是后面很多地方都用到的基础的路径。 if(isset($config['basePath'])) { $this->setBasePath($config['basePath']); unset($config['basePath']); } //tag2 $this->registerCoreComponents(); $this->configure($config); $this->preloadComponents(); $this->init(); }
//tag 这句代码很关键,我们在任何地方调用Yii::app()这个对象就是这就代码实现的 它将创建的CWebApplication实例传入YiiBase::setApplication($app); 代码如下: public static function setApplication($app) { if(self::$_app===null || $app===null) self::$_app=$app; else throw new CException(Yii::t('yii','Yii application can only be created once.')); }
//tag2 $this->registerCoreComponents(); 这个方法用来注册核心组件。 这个函数虽然是在CApplication的构造函数里面声明的,但是根据继承关系,当new CWebApplication的时候,这个构造函数实际上是在CWebApplication里面执行的,而registerCoreComponents()方法,在CWebApplication和CApplication里面都有声明,并且在CWebApplication->registerCoreComponents()里面有调用parent::registerCoreComponents();所以是这样的: 在CWebApplication里面注册的核心组件有:session,assetManager,user,themeManager,authManager,clientScript,widgetFactory 在CApplication里面注册的核心组件有:coreMessages,db,messages,errorHandler,securityManager,statePersister,urlManager,request,format 在这个方法最后调用了$this->setComponents($components);
下面trace 这个方法,有玄机
3.1 system.base.CModule.setComponents()
public function setComponents($components,$merge=true) { //这里的$components就是上面的配置数组 foreach($components as $id=>$component) $this->setComponent($id,$component,$merge); } public function setComponent($id,$component,$merge=true) { //这里当设置某个component的配置项是null,那么就相当于从这里删除掉了 if($component===null) { unset($this->_components[$id]); return; } //这里的$component显然是数组,并不是这个接口类的实例,所以不会进入这个if,它出现的意义暂不深究 elseif($component instanceof IApplicationComponent) { $this->_components[$id]=$component; if(!$component->getIsInitialized()) $component->init(); return; } //在一个请求到来,系统初始化的时候,$this->_components这个数组肯定是空的,下面这个if的意义在于在系统初始化之后(执行了CWepApplication和CApplication里面的registerCoreComponents()方法之后),当在protected/config/main.php配置数组里面配置有组件的数组,调用第三步构造函数里面的$this->configure($config);的时候,会使用魔术方法调用该方法,那么就会进入到这个if里面,最这个已经存在的component再进行配置修改。 elseif(isset($this->_components[$id])) { if(isset($component['class']) && get_class($this->_components[$id])!==$component['class']) { unset($this->_components[$id]); $this->_componentConfig[$id]=$component; //we should ignore merge here return; } foreach($component as $key=>$value) { if($key!=='class') $this->_components[$id]->$key=$value; } } elseif(isset($this->_componentConfig[$id]['class'],$component['class']) && $this->_componentConfig[$id]['class']!==$component['class']) { $this->_componentConfig[$id]=$component; //we should ignore merge here return; } //在这里处理合并配置项 if(isset($this->_componentConfig[$id]) && $merge) $this->_componentConfig[$id]=CMap::mergeArray($this->_componentConfig[$id],$component); //其实在系统初始化的时候,就是执行下面这一句话的,把registerCoreComponents()里面的配置数组,复制进CWepApplication的属性里面。 else $this->_componentConfig[$id]=$component; }
至于为什么在系统初始化的时候,先把核心组件的配置信息(其实就是组件类名)先写进CWepApplication的私有属性$_componentConfig里面,然后才将配置文件里面的组件配置信息更新进来。
我想原因应该是:自己在配置文件里面是不用关心系统启动需要的组件的,在框架里面先把框架启动需要的组件类先加进来,然后自己在配置文件里面的配置只要合并进来就好了。
3.2 system.base.CModule.configure()
这个函数里面也有玄机先上代码:
public function configure($config) { if(is_array($config)) { foreach($config as $key=>$value) $this->$key=$value; } }
乍看很普通,就是把$config里面的值,按照键值对,复制进对象里面的属性和值。
容易忽略一点是,在继承关系的最顶层是CComponent,里面有魔术方法__set(),看看这个魔术方法是什么样子(只取出关键代码):
public function __set($name,$value) { $setter='set'.$name; if(method_exists($this,$setter)) return $this->$setter($value); //下面的代码先省略 }
比如在/config/main.php里面配置了组件的配置信息,像这样:
'components'=>array( 'user'=>array( // enable cookie-based authentication 'allowAutoLogin'=>true, ), )
那么就相当于在configure()方法里面执行:$this->components = array();
因为类里面并没有声明components这个属性,所以对这个属性赋值的时候会调用魔术方法__set()
在魔术方法里面,先检查setcomponents()这个方法是否存在,当然是存在的,这个方法被声明在system.base.CModule里面;
所以,我们在/config/main.php里面对组件(components)进行配置的时候,那么配置项并不是简单的复制进CWepApplication的属性里面,而是调用了setComponents()方法。
3.3 system.base.CModule.preloadComponents()
预加载组件的加载在config/main.php里面的一个配置项:’preload’=>array(‘log’),
protected function preloadComponents() { //因为在__construct()里面之前已经调用了$this->configure($config); //所以这个$this->preload = array('log'); foreach($this->preload as $id) $this->getComponent($id); } public function getComponent($id,$createIfNull=true) { if(isset($this->_components[$id])) return $this->_components[$id]; elseif(isset($this->_componentConfig[$id]) && $createIfNull) { //肯定是进到这里面 //$this->_componentConfig是在__construct的$this->registerCoreComponents();里面初始化赋值的,log组件的初始化配置项只有一个class $config=$this->_componentConfig[$id]; if(!isset($config['enabled']) || $config['enabled']) { Yii::trace("Loading \"$id\" application component",'system.CModule'); unset($config['enabled']); $component=Yii::createComponent($config); $component->init(); return $this->_components[$id]=$component; } } }
3.4 system.web.CWepApplication.init()
在__construct里面最后一个函数是init()方法;//system.web.CWepApplication protected function init() { //父类的init()方法是空 parent::init(); // preload 'request' so that it has chance to respond to onBeginRequest event. $this->getRequest(); } //system.base.CApplication public function getRequest() { //这个方法前面也见过了,就是初始化一个组件,供一次请求的后续使用 return $this->getComponent('request'); }
至此一个请求的初始化就完成了。
<完>
相关文章推荐
- 从源码安装Mysql/Percona 5.5
- 插件管理框架 for Delphi(一)
- 浅析Ruby的源代码布局及其编程风格
- 使用CSS框架布局的缺点和优点小结
- 一起动手编写Android图片加载框架
- 基于.NET平台常用的框架和开源程序整理
- YII Framework框架教程之安全方案详解
- Yii PHP Framework实用入门教程(详细介绍)
- 列举PHP的Yii 2框架的开发优势
- PHP的Yii框架中Model模型的学习教程
- 详解PHP的Yii框架中自带的前端资源包的使用
- asp.net 抓取网页源码三种实现方法
- Windows窗体的.Net框架绘图技术实现方法
- 浅谈JavaScript 框架分类
- 轻量级javascript 框架Backbone使用指南
- javascript实现框架高度随内容改变的方法
- JS刷新框架外页面七种实现代码
- 超赞的动手创建JavaScript框架的详细教程
- 深入探讨前端框架react
- JS小游戏之仙剑翻牌源码详解