Yii PHP 框架分析(四)
2009-10-10 10:20
<table style="TABLE-LAYOUT: fixed; WIDTH: 100%"><tbody><tr><td> <div class="cnt" id="blog_text"> <p>Yii PHP 框架分析(四)<br/>作者:wdy</p> <p><a href="http://hi.baidu.com/delphiss/blog/item/c15b314f05f9dfc0d0c86a26.html">http://hi.baidu.com/delphiss/blog/item/c15b314f05f9dfc0d0c86a26.html</a></p> <p>Yii应用的入口脚本最后一句启动了WebApplication</p> <p>Yii::createWebApplication($config)->run();</p> <p>CApplication:</p> <p>public function run()<br/>{<br/> $this->onBeginRequest(new CEvent($this));<br/> $this->processRequest();<br/> $this->onEndRequest(new CEvent($this));<br/>}</p> <p>processRequest()开始处理请求,由CWebApplication实现:</p> <p>public function processRequest()<br/>{<br/> if(is_array($this->catchAllRequest) && isset($this->catchAllRequest[0]))<br/> {<br/> $route=$this->catchAllRequest[0];<br/> foreach(array_splice($this->catchAllRequest,1) as $name=>$value)<br/> $_GET[$name]=$value;<br/> }<br/> else<br/> $route=$this->getUrlManager()->parseUrl($this->getRequest());<br/> $this->runController($route);<br/>}</p> <p>urlManager应用组件的parseUrl() 创建了$route (形式为controllerID/actionID的字符串),runController()创建Controller对象开始处理http请求。</p> <p>$route 的值可能存在以下几种情况:<br/>- 为空: 用 defaultController 值代替;<br/>- “moduleID/controllerID/actionID”: module下的<br/>- “controllerID/actionID” : 最常见的形式<br/>- “folder1/folder2/controllerID/actionID” 多级目录下的控制器</p> <p>runController首先调用createController()创建控制器对象</p> <p><br/>public function createController($route,$owner=null)<br/>{<br/> // $owner为空则设置为$this,即 $_app对象<br/> if($owner===null)<br/> $owner=$this;<br/> // $route为空设置为defaultController,在$config里配置<br/> if(($route=trim($route,'/'))==='')<br/> $route=$owner->defaultController;<br/> $caseSensitive=$this->getUrlManager()->caseSensitive;</p> <p> $route.='/';<br/> // 逐一取出 $route 按 ‘/’分割后的第一段进行处理<br/> while(($pos=strpos($route,'/'))!==false)<br/> {<br/> // $id 里存放的是 $route 第一个 ‘/’前的部分<br/> $id=substr($route,0,$pos);<br/> if(!preg_match('/^\w+$/',$id))<br/> return null;<br/> if(!$caseSensitive)<br/> $id=strtolower($id);<br/> // $route 存放’/’后面部分<br/> $route=(string)substr($route,$pos+1);<br/> if(!isset($basePath)) // 完整$route的第一段<br/> {<br/> // 如果$id在controllerMap[]里做了映射<br/> // 直接根据$id创建controller对象<br/> if(isset($owner->controllerMap[$id]))<br/> {<br/> return array(<br/> Yii::createComponent($owner->controllerMap[$id],$id,$owner===$this?null:$owner),<br/> $this->parseActionParams($route),<br/> );<br/> }</p> <p> // $id 是系统已定义的 module,根据$id取得module对象作为$owner参数来createController<br/> if(($module=$owner->getModule($id))!==null)<br/> return $this->createController($route,$module);<br/> // 控制器所在的目录<br/> $basePath=$owner->getControllerPath();<br/> $controllerID='';<br/> }<br/> else<br/> $controllerID.='/';<br/> $className=ucfirst($id).'Controller';<br/> $classFile=$basePath.DIRECTORY_SEPARATOR.$className.'.php';<br/> // 控制器类文件存在,则require并创建控制器对象&返回<br/> if(is_file($classFile))<br/> {<br/> if(!class_exists($className,false))<br/> require($classFile);<br/> if(class_exists($className,false) && is_subclass_of($className,'CController'))<br/> {<br/> $id[0]=strtolower($id[0]);<br/> return array(<br/> new $className($controllerID.$id,$owner===$this?null:$owner),<br/> $this->parseActionParams($route),<br/> );<br/> }<br/> return null;<br/> }<br/> // 未找到控制器类文件,可能是多级目录,继续往子目录搜索<br/> $controllerID.=$id;<br/> $basePath.=DIRECTORY_SEPARATOR.$id;<br/> }<br/>}</p> <p><br/>createController() 返回一个创建好的控制器对象和actionID, runController()调用控制器的init()方法和run($actionID)来运行控制器:</p> <p>public function runController($route)<br/>{<br/> if(($ca=$this->createController($route))!==null)<br/> {<br/> list($controller,$actionID)=$ca;<br/> $oldController=$this->_controller;<br/> $this->_controller=$controller;<br/> $controller->init();<br/> $controller->run($actionID);<br/> $this->_controller=$oldController;<br/> }<br/> else<br/> throw new CHttpException( 404, Yii::t('yii','Unable to resolve the request "{route}".', array( '{route}'=>$route==='' ? $this->defaultController:$route)));<br/>}</p> <p>$controller->init()里没有动作, run():</p> <p>public function run($actionID)<br/>{<br/> if(($action=$this->createAction($actionID))!==null)<br/> {<br/> if(($parent=$this->getModule())===null)<br/> $parent=Yii::app();<br/> if($parent->beforeControllerAction($this,$action))<br/> {<br/> $this->runActionWithFilters($action,$this->filters());<br/> $parent->afterControllerAction($this,$action);<br/> }<br/> }<br/> else<br/> $this->missingAction($actionID);<br/>}</p> <p>$controller->run($actionID)里首先创建了Action对象:</p> <p>public function createAction($actionID)<br/>{<br/> // 为空设置为defaultAction<br/> if($actionID==='')<br/> $actionID=$this->defaultAction;<br/> // 控制器里存在 'action'.$actionID 的方法,创建CInlineAction对象<br/> if(method_exists($this,'action'.$actionID) && strcasecmp($actionID,'s')) // we have actions method<br/> return new CInlineAction($this,$actionID);<br/> // 否则根据actions映射来创建Action对象<br/> else<br/> return $this->createActionFromMap($this->actions(),$actionID,$actionID);<br/>}</p> <p>这里可以看到控制器并不是直接调用了action方法,而是需要一个Action对象来运行控制器动作,这样就统一了控制器方法和actions映射的action对象对action的处理,即两种形式的action处理都统一为IAction接口的run()调用。</p> <p>IAction接口要求实现run(),getId(),getController () 三个方法,Yii提供的CAction类要求构造函数提供Controller和Id并实现了getId()和getController ()的处理,Action类从CAction继承即可。</p> <p>CInlineAction在web/action下,run()是很简单的处理过程,调用了Controller的action方法:</p> <p>class CInlineAction extends CAction<br/>{<br/>public function run()<br/>{<br/> $method='action'.$this->getId();<br/> $this->getController()->$method();<br/>}<br/>}</p> <p>回到 $controller->run($actionID)</p> <p>public function run($actionID)<br/>{<br/> if(($action=$this->createAction($actionID))!==null)<br/> {<br/> if(($parent=$this->getModule())===null)<br/> $parent=Yii::app();<br/> if($parent->beforeControllerAction($this,$action))<br/> {<br/> $this->runActionWithFilters($action,$this->filters());<br/> $parent->afterControllerAction($this,$action);<br/> }<br/> }<br/> else<br/> $this->missingAction($actionID);<br/>}</p> <p>Yii::app()->beforeControllerAction() 实际是固定返回true的,所以action对象实际是通过控制器的runActionWithFilters()被run的</p> <p>public function runActionWithFilters($action,$filters)<br/>{<br/> // 控制器里没有设置过滤器<br/> if(empty($filters))<br/> $this->runAction($action);<br/> else<br/> {<br/> // 创建过滤器链对象并运行<br/> $priorAction=$this->_action;<br/> $this->_action=$action;<br/> CFilterChain::create($this,$action,$filters)->run();<br/> $this->_action=$priorAction;<br/> }<br/>}</p> <p>没有过滤器,runAction()就是最终要调用前面创建的action对象的run()方法:</p> <p>public function runAction($action)<br/>{<br/> $priorAction=$this->_action;<br/> $this->_action=$action;<br/> if($this->beforeAction($action))<br/> {<br/> $action->run();<br/> $this->afterAction($action);<br/> }<br/> $this->_action=$priorAction;<br/>}</p> <p>每个filter都要实现IFilter接口,filter实现的preFilter()方法在$action->run()之前调用,如果判断action可以执行则返回true,否则返回false</p> <p>if($filter1->preFilter())<br/>if($filter2->preFilter())<br/> if($filtern->preFilter())<br/> $action->run()<br/> $filtern->postFilter()<br/> $filter2->postFilter()<br/>$filter1->postFilter()</p> <p>在action里最常见的操作就是render view文件: renderPartial()和render()。render()在处理view文件后会把结果放入layout文件内。</p> <p>public function renderPartial($view,$data=null,$return=false,$processOutput=false)<br/>{<br/> if(($viewFile=$this->getViewFile($view))!==false)<br/> {<br/> $output=$this->renderFile($viewFile,$data,true);<br/> if($processOutput)<br/> $output=$this->processOutput($output);<br/> if($return)<br/> return $output;<br/> else<br/> echo $output;<br/> }<br/> else<br/> throw new CException(Yii::t('yii','{controller} cannot find the requested view "{view}".',<br/> array('{controller}'=>get_class($this), '{view}'=>$view)));<br/>}</p> <p><br/>getViewFile($view)获得$view的完整路径:<br/>$view 以 ‘/’开头的,以系统views目录作为起始目录+$view+.php<br/>$view含有别名的,查找别名的真实路径<br/>其他的以modele view目录作为起始目录+$view+.php</p> <p>如果没有在$config里配置第三方的renderer,renderFile() 里实际是调用了yii自身提供的renderInternal()来render view文件:</p> <p>public function renderFile($viewFile,$data=null,$return=false)<br/>{<br/> $widgetCount=count($this->_widgetStack);<br/> // 如果配置了其他的ViewRenderer<br/> if(($renderer=Yii::app()->getViewRenderer())!==null)<br/> $content=$renderer->renderFile($this,$viewFile,$data,$return);<br/> else<br/> // yii 自身的render<br/> $content=$this->renderInternal($viewFile,$data,$return);<br/> if(count($this->_widgetStack)===$widgetCount)<br/> return $content;<br/> else<br/> {<br/> $widget=end($this->_widgetStack);<br/> throw new CException(Yii::t('yii','{controller} contains improperly nested widget tags in its view "{view}". A {widget} widget does not have an endWidget() call.',<br/> array('{controller}'=>get_class($this), '{view}'=>$viewFile, '{widget}'=>get_class($widget))));<br/> }<br/>}</p> <p>Yii的renderer用的是php本身作为模板系统:</p> <p>public function renderInternal($_viewFile_,$_data_=null,$_return_=false)<br/>{<br/> // extract函数将$_data_从数组中将变量导入到当前的符号表<br/> if(is_array($_data_))<br/> extract($_data_,EXTR_PREFIX_SAME,'data');<br/> else<br/> $data=$_data_;<br/> if($_return_)<br/> {<br/> ob_start();<br/> ob_implicit_flush(false);<br/> require($_viewFile_);<br/> return ob_get_clean();<br/> }<br/> else<br/> require($_viewFile_);<br/>}</p> <p>render()的实际上是先renderPartial view文件,然后renderFile layoutfile,并将view文件的结果做为$content变量传入。</p> <p>public function render($view,$data=null,$return=false)<br/>{<br/> $output=$this->renderPartial($view,$data,true);<br/> if(($layoutFile=$this->getLayoutFile($this->layout))!==false)<br/> $output=$this->renderFile($layoutFile,array('content'=>$output),true);</p> <p> $output=$this->processOutput($output);</p> <p> if($return)<br/> return $output;<br/> else<br/> echo $output;<br/>}</p> <p>processOutput将render的结果再做处理,比如在head加上css或js脚本等。</p> <p>public function processOutput ($output)<br/>{<br/> Yii::app()->getClientScript()->render($output);</p> <p> // if using page caching, we should delay dynamic output replacement<br/> if($this->_dynamicOutput!==null && $this->isCachingStackEmpty())<br/> $output=$this->processDynamicOutput($output);</p> <p> if($this->_pageStates===null)<br/> $this->_pageStates=$this->loadPageStates();<br/> if(!empty($this->_pageStates))<br/> $this->savePageStates($this->_pageStates,$output);</p> <p> return $output;<br/>}</p> <p/></div></td></tr></tbody></table>