本篇主要讲述yii是如何处理一个web请求的,其中包括了route,filter,controller,action等等。他是如何安排他们各自的顺序,同时又预留了哪些事件函数,以让开发者更好的控制。本文需要一定的编程基础和对yii有一定熟悉,属于进阶型的。另外,由于程序庞大,比较复杂,请千万看准,哪段程序是在哪个类中的。

Ready! Start...

首先要说的肯定是index.php,他作为整个application的入口文件,起到了初始化各种变量的作用,接下来看代码,在这里我们约定下,这里所有的代码都一般情况下的

 

1 
run();

在这里唯一要关心的,就是最后一句,因为这句开始了我们的Request Process,让我们接下来看看CreateWebApplication做了什么操作?

 

1 //创建一个WebApplication  2     public static function createWebApplication($config=null)  3     {
4 return self::createApplication('CWebApplication',$config); 5 } 6 7 //创建了一恶搞CWebApplication的实例 8 public static function createApplication($class,$config=null) 9 {
10 return new $class($config); 11 } 12 13 //因为CWebApplication没有特别定义的构造函数 14 //其又是继承CApplication的,所以会执行CApplication的构造函数 15 //下面是CApplicaiton的构造函数 16 public function __construct($config=null) 17 {
18 Yii::setApplication($this); 19 20 // set basePath at early as possible to avoid trouble 21 if(is_string($config)) 22 $config=require($config); 23 if(isset($config['basePath'])) 24 {
25 $this->setBasePath($config['basePath']); 26 unset($config['basePath']); 27 } 28 else 29 $this->setBasePath('protected'); 30 Yii::setPathOfAlias('application',$this->getBasePath()); 31 Yii::setPathOfAlias('webroot',dirname($_SERVER['SCRIPT_FILENAME'])); 32 Yii::setPathOfAlias('ext',$this->getBasePath().DIRECTORY_SEPARATOR.'extensions'); 33 34 $this->preinit(); 35 36 $this->initSystemHandlers(); 37 $this->registerCoreComponents(); 38 39 $this->configure($config); 40 $this->attachBehaviors($this->behaviors); 41 $this->preloadComponents(); 42 43 $this->init(); 44 }

仔细阅读代码中的注释,然后,分析代码,我们容易发现,整个构造函数,利用main.php中的configuration对整个app实例进行了变量初始化,分别包括

1、设置基地址25行或29行
2、设置了Alias,现在知道为什么你用namespace的格式,文件能找到路径的原因之一了吧。30-32行
3、preinit这个是预留的函数,你可以在自己扩展的Application中override这个,帮助控制。
4、initSystemHandlers这个是用于初始化Yii的ExceptionHandler的,当前前提是你开启了。
5、registerCoreComponents,名字上就很容易理解,注册核心控件,这里面有一些必须加载的component,起主要的工作就是将component存入APP->_components中(这里的APP表示Application实例),注意存的时候还是一个实例化的过程。这里我们截取一段代码,为了更好的理解。现在这里和main.php中的配置是没有关系的,因为这里都是必须加载的,稍后,会有个过程,将一些设置根据main.php的配置进行修改。

1     public function setComponent($id,$component)  2     {
3 if($component===null) 4 unset($this->_components[$id]); 5 else 6 {
7 $this->_components[$id]=$component; 8 if(!$component->getIsInitialized()) 9 $component->init(); 10 } 11 }

上面的代码是注册核心组件最核心的部分。注意第7-9行。由此可以看出,_components中存入的是各个component的实例。上面的代码在CModule.php文件中,因为CApplication是继承自CModule的。这里还有个变量是需要注意的是_componentConfig,我们看一下代码(来自CModule.php),下面的代码会调用到上面的函数

1     public function setComponents($components,$merge=true)  2     {
3 foreach($components as $id=>$component) 4 {
5 if($component instanceof IApplicationComponent) 6 $this->setComponent($id,$component); 7 else if(isset($this->_componentConfig[$id]) && $merge) 8 $this->_componentConfig[$id]=CMap::mergeArray($this->_componentConfig[$id],$component); 9 else 10 $this->_componentConfig[$id]=$component; 11 } 12 }

第6行使用到了,我们讲解的方法,但是在这里不是我们的重点,我们着重说的是第7-10行,根据前面的逻辑我们知道只有非IApplicationComponent的实例才可以存入_componentConfig中,那么什么是IApplicationComponent呢?“After the application completes configuration, it will invoke the {@link init()} method of every loaded application component.”上面是相关的文档注释,他是这么解释IApplicationComponent的,其实说白了,就是有init方法,因为在setComponent方法中就用到了,见第9行。典型的这类Component有哪些呢?还真不少,举几个比如CCache,CAssetManager,CClientScript等,个人感觉就就是默认是preload的那些。另外,一些,比如CLogger什么的就不是了,他会被存放在_componentConfig中。

6、configure,这个是将main.php中的关联数组,转成此application的属性。以后再使用的时候,就是直接使用属性了,不在用这个数组。但是需要注意的是他这里的属性是指component,import,module这些第一纬的数组键,并不是其中的,类似request,urlManager这类的。
这里是有必要详细说下的,因为这里使用了__set,__get来简化操作,同时如果不搞清这个机制的话,在阅读其他yii的代码会遇到一定障碍的。接下来看代码,configure方法在CModule.php中

1     public function configure($config)  2     {
3 if(is_array($config)) 4 {
5 foreach($config as $key=>$value) 6 $this->$key=$value; 7 } 8 } 9 10 //In the CModule 11 public function __get($name) 12 {
13 if($this->hasComponent($name)) 14 return $this->getComponent($name); 15 else 16 return parent::__get($name); 17 } 18 19 20 //In the CComponent,__set存在于此中, 21 //CWebApplication,CApplication,CModule中是没有的 22 public function __set($name,$value) 23 {
24 $setter='set'.$name; 25 if(method_exists($this,$setter)) 26 return $this->$setter($value); 27 else if(strncasecmp($name,'on',2)===0 && method_exists($this,$name)) 28 {
29 // duplicating getEventHandlers() here for performance 30 $name=strtolower($name); 31 if(!isset($this->_e[$name])) 32 $this->_e[$name]=new CList; 33 return $this->_e[$name]->add($value); 34 } 35 else if(is_array($this->_m)) 36 {
37 foreach($this->_m as $object) 38 {
39 if($object->getEnabled() && (property_exists($object,$name) || $object->canSetProperty($name))) 40 return $object->$name=$value; 41 } 42 } 43 if(method_exists($this,'get'.$name)) 44 throw new CException(Yii::t('yii','Property "{class}.{property}" is read only.', 45 array('{class}'=>get_class($this), '{property}'=>$name))); 46 else 47 throw new CException(Yii::t('yii','Property "{class}.{property}" is not defined.', 48 array('{class}'=>get_class($this), '{property}'=>$name))); 49 }

其实,很多configure的属性是不存在的,也就是无法访问的。比如$this->request之类的,这个时候因为不存在所以就会在读取的时候调用__get,存入的时候调用__set。接下来,我们就看看,读取和存入这些不存在的属性的时候是如何处理的吧。先看读取,就是__get方法,看行13,我们看得到了他判断是否存在_component和_componentConfig两个数组中是否有,有的话就直接从其中读取。这里就说,他会有限判断看看是不是在这两个数组中存在。那么在__set中又是如何处理的呢?前面我们提到过,这里的$key是main.php的数组中第一维的key。这里我们以$key="componnets"作为例子,进行说明,在执行第6行时,会调用__set方法,24行将其拼贴成了一个setter函数,名为setComponents方法,因为该方法是存在,所以就执行该方法。我们看下该方法的代码,

1     public function setComponents($components,$merge=true)  2     {
3 foreach($components as $id=>$component) 4 {
5 if($component instanceof IApplicationComponent) 6 $this->setComponent($id,$component); 7 else if(isset($this->_componentConfig[$id]) && $merge) 8 $this->_componentConfig[$id]=CMap::mergeArray($this->_componentConfig[$id],$component); 9 else 10 $this->_componentConfig[$id]=$component; 11 } 12 }

这里就很明显了,就是把main.php中components里面的内容存入了_component或者_componentConfig中。至此完成了,整个configure的过程。现在我们以$this->request为例,简要说明下是程序是如何获取的。正常是不含有这个属性的,所以此时如果是read就会访问__get,就会从_component或_componentConfig中取,如果是write,那么就会走__set因为其中没有符合的项(也可以认为没有对应的setter),所以是不可写的。

7、attachBehaviors就是注册Application的Behavior其实就是一个事件集合。

8、preloadComponents,这个是将前面configure中从main.php中导入的preload设置的component,他和前面的corecomponent一样,会存入_components中,并且完成初始化。

9、最后的是init,这个过程执行的是CWebApplication中的init() 

 

1     protected function init() 2     {
3 parent::init(); 4 // preload 'request' so that it has chance to respond to onBeginRequest event. 5 $this->getRequest(); 6 }

该getRequest方法就是返回request Component,在CApplication.php中的第957行,默认使用的是CHttpRequest.注意这个request是从_component中出来的,因为其实一个IApplicationComponent。

这里要顺带一提,在yii大量使用了重写__set,__get函数,所以你虽然会看到$this->$key=$value,但是有可能是经过重写的,并不一定就是一个属性,而可能是个setter。结合步骤6,好好理解yii中的getter和setter应用,不过需要注意的是,只有当不存在对应属性时才会调用该方法。
言归正传,这里相对比较绕,我把相关的代码贴出来,结合代码看会简单很多。

 

1     //In the CWebApplication  2         protected function init()  3     {
4 parent::init(); 5 // preload 'request' so that it has chance to respond to onBeginRequest event. 6 $this->getRequest(); 7 } 8 9 //In the CApplicaiton 10 public function getRequest() 11 {
12 return $this->getComponent('request'); 13 } 14 15 16 //In the CModule 17 public function getComponent($id,$createIfNull=true) 18 {
19 if(isset($this->_components[$id])) 20 return $this->_components[$id]; 21 else if(isset($this->_componentConfig[$id]) && $createIfNull) 22 {
23 $config=$this->_componentConfig[$id]; 24 if(!isset($config['enabled']) || $config['enabled']) 25 {
26 Yii::trace("Loading \"$id\" application component",'system.CModule'); 27 unset($config['enabled']); 28 $component=Yii::createComponent($config); 29 $component->init(); 30 return $this->_components[$id]=$component; 31 } 32 } 33 }

注释说明了此段代码所属的类,下面的方法被上面的方法调用,让我们看getComponent方法,注意20行,此处获得了request component对应的实例。这里要说下第28行,在之前虽然这个的步骤中preloadComponents方法中,已经将main.php中的configure用来实例化对应的component了,那么剩下的都是非compnoent的,这里的28行,可以理解为是中延迟加载(初始化),根据$config创建个component实例,存入_componentConfig中。

好了终于讲解完application的construct过程了。