我们可以接下来继续分析了。走了一大圈,我们其实还在下面代码处
Yii::createWebApplication($config)->run();
前面返回了一个Application的实例,对于web其实就是CWebApplication的实例,然后执行run方法,该run方法在CApplicaiton中,代码如下
public function run()
{
if($this->hasEventHandler('onBeginRequest'))
$this->onBeginRequest(new CEvent($this));
$this->processRequest();
if($this->hasEventHandler('onEndRequest'))
$this->onEndRequest(new CEvent($this));
}
这段代码逻辑很清晰,主要是两个事件,一个是onBeginRequest另一个是onEndRequest,这里是预留给我们扩展的。这里的最主要的是$this->processRequest();语句
public function processRequest()
{
if(is_array($this->catchAllRequest) && isset($this->catchAllRequest[0]))
{
$route=$this->catchAllRequest[0];
foreach(array_splice($this->catchAllRequest,1) as $name=>$value)
$_GET[$name]=$value;
}
else
$route=$this->getUrlManager()->parseUrl($this->getRequest());
$this->runController($route);
}
查看该方法,这里就是根据相应的情况生成route,有了route后自然就可以运行Controller了。此时因为route只是生成,app还不知道需要访问哪个Controller。所以这个runController其实还有初始化对应Controller的作用。看一下代码
public function runController($route)
{
if(($ca=$this->createController($route))!==null)
{
list($controller,$actionID)=$ca;
$oldController=$this->_controller;
$this->_controller=$controller;
$controller->init();
$controller->run($actionID);
$this->_controller=$oldController;
}
else
throw new CHttpException(404,Yii::t('yii','Unable to resolve the request "{route}".',
array('{route}'=>$route===''?$this->defaultController:$route)));
}
当route正确时候,可以创建出相应的controller。然后根据最主要是init方法和run($actionID)方法。init方法在前面已经介绍过了,在默认的yii程序中,实际上是没有任何作用的。
这里我们要看下createAction方法,对于action如何生成,对于后面的action调用的方法理解很重要,见代码
1 public function createAction($actionID)
2 {
3 if($actionID==='')
4 $actionID=$this->defaultAction;
5 if(method_exists($this,'action'.$actionID) && strcasecmp($actionID,'s')) // we have actions method
6 return new CInlineAction($this,$actionID);
7 else
8 {
9 $action=$this->createActionFromMap($this->actions(),$actionID,$actionID);
10 if($action!==null && !method_exists($action,'run'))
11 throw new CException(Yii::t('yii', 'Action class {class} must implement the "run" method.', array('{class}'=>get_class($action))));
12 return $action;
13 }
14 }
有了上面的基础,我们这里主要看run($actionID),继续看代码。该run方法是在CController中的
1 public function run($actionID)
2 {
3 if(($action=$this->createAction($actionID))!==null)
4 {
5 if(($parent=$this->getModule())===null)
6 $parent=Yii::app();
7 if($parent->beforeControllerAction($this,$action))
8 {
9 $this->runActionWithFilters($action,$this->filters());
10 $parent->afterControllerAction($this,$action);
11 }
12 }
13 else
14 $this->missingAction($actionID);
15 }
此段代码表示,在请求的action存在时,执行相应的action。第5行表示,首先看看当前controller中有没有action,有的话就返回CInlineAction,其中可是有run方法的哦。后面会用到的。否则,则是一个Action类,就需要做map了。本来嘛Yii中就是这两种Action的设置方法,后者可以带来很好的复用。
接下来,我们需要注意这么几个方法,CWebApplication.beforeControllerAction方法,CController.runActionWithFilters方法,最后是CWebApplication.afterControllerAction方法。对于第一和第三个方法,都是用来override的,这样的话可以在执行Action做一些操作,并且这些操作是在Filter执行前或在action执行完后的。这里我们着重要看的是runActionWithFilter方法,看代码。
1 //In the CController
2 public function runActionWithFilters($action,$filters)
3 {
4 if(empty($filters))
5 $this->runAction($action);
6 else
7 {
8 $priorAction=$this->_action;
9 $this->_action=$action;
10 CFilterChain::create($this,$action,$filters)->run();
11 $this->_action=$priorAction;
12 }
13 }
此段代码很简单,就是如果没有filter的话,直接执行Action,如果有filter的话,则要先执行所有的filter。核心代码是第10行。让我们来看看代码
1 public static function create($controller,$action,$filters)
2 {
3 $chain=new CFilterChain($controller,$action);
4
5 $actionID=$action->getId();
6 foreach($filters as $filter)
7 {
8 if(is_string($filter)) // filterName [+|- action1 action2]
9 {
10 if(($pos=strpos($filter,'+'))!==false || ($pos=strpos($filter,'-'))!==false)
11 {
12 $matched=preg_match("/\b{$actionID}\b/i",substr($filter,$pos+1))>0;
13 if(($filter[$pos]==='+')===$matched)
14 $filter=CInlineFilter::create($controller,trim(substr($filter,0,$pos)));
15 }
16 else
17 $filter=CInlineFilter::create($controller,$filter);
18 }
19 else if(is_array($filter)) // array('path.to.class [+|- action1, action2]','param1'=>'value1',...)
20 {
21 if(!isset($filter[0]))
22 throw new CException(Yii::t('yii','The first element in a filter configuration must be the filter class.'));
23 $filterClass=$filter[0];
24 unset($filter[0]);
25 if(($pos=strpos($filterClass,'+'))!==false || ($pos=strpos($filterClass,'-'))!==false)
26 {
27 $matched=preg_match("/\b{$actionID}\b/i",substr($filterClass,$pos+1))>0;
28 if(($filterClass[$pos]==='+')===$matched)
29 $filterClass=trim(substr($filterClass,0,$pos));
30 else
31 continue;
32 }
33 $filter['class']=$filterClass;
34 $filter=Yii::createComponent($filter);
35 }
36
37 if(is_object($filter))
38 {
39 $filter->init();
40 $chain->add($filter);
41 }
42 }
43 return $chain;
44 }
这段程序,其实就是根据我们在controller中的filter()中配置的filter,生成一个filterchain,filterchain其实是一个filter的集合。此段代码还根据配置挨个生成了filter
获得了该controller的filterchain后,随后执行了run()方法,我们继续看看run方法的代码。
1 public function run()
2 {
3 if($this->offsetExists($this->filterIndex))
4 {
5 $filter=$this->itemAt($this->filterIndex++);
6 Yii::trace('Running filter '.($filter instanceof CInlineFilter ? get_class($this->controller).'.filter'.$filter->name.'()':get_class($filter).'.filter()'),'system.web.filters.CFilterChain');
7 $filter->filter($this);
8 }
9 else
10 $this->controller->runAction($this->action);
11 }
这段程序当有未执行的filter时就运行filter,否则就运行相应Action。这里其实用到了一个迭代$filter->filter($this);,此处我们需要看CFilter中的filter方法的代码,
1 /**
2 * Performs the filtering.
3 * The default implementation is to invoke {@link preFilter}
4 * and {@link postFilter} which are meant to be overridden
5 * child classes. If a child class needs to override this method,
6 * make sure it calls <code>$filterChain->run()</code>
7 * if the action should be executed.
8 * @param CFilterChain $filterChain the filter chain that the filter is on.
9 */
10 public function filter($filterChain)
11 {
12 if($this->preFilter($filterChain))
13 {
14 $filterChain->run();
15 $this->postFilter($filterChain);
16 }
17 }
这里我把注释也搬过来了。请注意第6行,“请保证会调用$filterChain->run()”,回到FilterChain的run方法中,我们不难发现,其实他是将自身传给了单个filter,然后filter会再让其运行,在FilterChain中通过$filterIndex来作为游标,标定处理到哪个了。知道全部处理过后,运行runAction。接下来让我们看runAction方法
1 public function runAction($action)
2 {
3 $priorAction=$this->_action;
4 $this->_action=$action;
5 if($this->beforeAction($action))
6 {
7 if($action->runWithParams($this->getActionParams())===false)
8 $this->invalidActionParams($action);
9 else
10 $this->afterAction($action);
11 }
12 $this->_action=$priorAction;
13 }
我们又在此看到了,预留的函数beforeAction和afterAction,这里就不多做解释了。关键看第7,8行。
先看第8行,第8行就是当Action执行失败时,运行的,yii默认在里面只是做了个抛出异常的操作。接下来看第7行。
这里的runWithParams方法是可以override的,所以需要注意多态性。我们将定传进来的是CInlineAction吧。
它继承了CAction,这个代表我们在controller中直接以action作为前缀的Action所对应的类。
1 //CAction
2 public function __construct($controller,$id)
3 {
4 $this->_controller=$controller;
5 $this->_id=$id;
6 }
7
8
9 //CInlineAction
10 public function run()
11 {
12 $method='action'.$this->getId();
13 $this->getController()->$method();
14 }
15
16 //CInlineAction
17 public function runWithParams($params)
18 {
19 $methodName='action'.$this->getId();
20 $controller=$this->getController();
21 $method=new ReflectionMethod($controller, $methodName);
22 if($method->getNumberOfParameters()>0)
23 return $this->runWithParamsInternal($controller, $method, $params);
24 else
25 return $controller->$methodName();
26 }
首先看下构造函数,这样你就很容易明白,这个CInlineAction是如何与Controller关联的,就是就是将当前的controller内聚到此Action中,然后调用该controller的对应action方法。接下来看runWithParams方法。runWithParams就是用来判定action的函数签名是否有参数,有的话,就把传入$param运行,否则就直接运行action。run方法更简单,直接就是执行相应的action,但是你要注意这里的run和runWithParams实际上是有功能相似性的(一个可以带参,一个不带),根据实际情况选用即可。支持action运行完了。我们就可以一层一层的跳出去了。随后运行CController.runAtion方法中的afterAction语句,现在就等于执行完了,语句CFilterChain::create($this,$action,$filters)->run();(位于CController.runActionWithFilters方法中),之后又回到了CController.run方法中,执行afterControllerAction方法。之后再回到CWebApplication.runController方法中,之后再回到CWebApplication.processRequest方法中,之后再回到CApplication.run方法中,如果存在onEndRequest handler则运行之。支持整个Yii的Request Process Flow运行完毕了。
内容很多,但是很有必要好好研究,这对于整个request控制大有益处,尤其是对几个预留的处理函数,另外,behavior和component的加载