ThinkChat🤖让你学习和工作更高效,注册即送10W Token,即刻开启你的AI之旅 广告
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)-&gt;run();</p> <p>CApplication:</p> <p>public function run()<br/>{<br/>   $this-&gt;onBeginRequest(new CEvent($this));<br/>   $this-&gt;processRequest();<br/>   $this-&gt;onEndRequest(new CEvent($this));<br/>}</p> <p>processRequest()开始处理请求,由CWebApplication实现:</p> <p>public function processRequest()<br/>{<br/>   if(is_array($this-&gt;catchAllRequest) &amp;&amp; isset($this-&gt;catchAllRequest[0]))<br/>   {<br/>    $route=$this-&gt;catchAllRequest[0];<br/>    foreach(array_splice($this-&gt;catchAllRequest,1) as $name=&gt;$value)<br/>     $_GET[$name]=$value;<br/>   }<br/>   else<br/>    $route=$this-&gt;getUrlManager()-&gt;parseUrl($this-&gt;getRequest());<br/>   $this-&gt;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-&gt;defaultController;<br/>   $caseSensitive=$this-&gt;getUrlManager()-&gt;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-&gt;controllerMap[$id]))<br/>     {<br/>      return array(<br/>       Yii::createComponent($owner-&gt;controllerMap[$id],$id,$owner===$this?null:$owner),<br/>       $this-&gt;parseActionParams($route),<br/>      );<br/>     }</p> <p>     // $id 是系统已定义的 module,根据$id取得module对象作为$owner参数来createController<br/>     if(($module=$owner-&gt;getModule($id))!==null)<br/>      return $this-&gt;createController($route,$module);<br/>     // 控制器所在的目录<br/>     $basePath=$owner-&gt;getControllerPath();<br/>     $controllerID='';<br/>    }<br/>    else<br/>     $controllerID.='/';<br/>    $className=ucfirst($id).'Controller';<br/>    $classFile=$basePath.DIRECTORY_SEPARATOR.$className.'.php';<br/>    // 控制器类文件存在,则require并创建控制器对象&amp;返回<br/>    if(is_file($classFile))<br/>    {<br/>     if(!class_exists($className,false))<br/>      require($classFile);<br/>     if(class_exists($className,false) &amp;&amp; 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-&gt;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-&gt;createController($route))!==null)<br/>   {<br/>    list($controller,$actionID)=$ca;<br/>    $oldController=$this-&gt;_controller;<br/>    $this-&gt;_controller=$controller;<br/>    $controller-&gt;init();<br/>    $controller-&gt;run($actionID);<br/>    $this-&gt;_controller=$oldController;<br/>   }<br/>   else<br/>    throw new CHttpException( 404, Yii::t('yii','Unable to resolve the request "{route}".', array( '{route}'=&gt;$route==='' ? $this-&gt;defaultController:$route)));<br/>}</p> <p>$controller-&gt;init()里没有动作, run():</p> <p>public function run($actionID)<br/>{<br/>   if(($action=$this-&gt;createAction($actionID))!==null)<br/>   {<br/>    if(($parent=$this-&gt;getModule())===null)<br/>     $parent=Yii::app();<br/>    if($parent-&gt;beforeControllerAction($this,$action))<br/>    {<br/>     $this-&gt;runActionWithFilters($action,$this-&gt;filters());<br/>     $parent-&gt;afterControllerAction($this,$action);<br/>    }<br/>   }<br/>   else<br/>    $this-&gt;missingAction($actionID);<br/>}</p> <p>$controller-&gt;run($actionID)里首先创建了Action对象:</p> <p>public function createAction($actionID)<br/>{<br/>   // 为空设置为defaultAction<br/>   if($actionID==='')<br/>    $actionID=$this-&gt;defaultAction;<br/>   // 控制器里存在 'action'.$actionID 的方法,创建CInlineAction对象<br/>   if(method_exists($this,'action'.$actionID) &amp;&amp; strcasecmp($actionID,'s')) // we have actions method<br/>    return new CInlineAction($this,$actionID);<br/>   // 否则根据actions映射来创建Action对象<br/>   else<br/>    return $this-&gt;createActionFromMap($this-&gt;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-&gt;getId();<br/>   $this-&gt;getController()-&gt;$method();<br/>}<br/>}</p> <p>回到 $controller-&gt;run($actionID)</p> <p>public function run($actionID)<br/>{<br/>   if(($action=$this-&gt;createAction($actionID))!==null)<br/>   {<br/>    if(($parent=$this-&gt;getModule())===null)<br/>     $parent=Yii::app();<br/>    if($parent-&gt;beforeControllerAction($this,$action))<br/>    {<br/>     $this-&gt;runActionWithFilters($action,$this-&gt;filters());<br/>     $parent-&gt;afterControllerAction($this,$action);<br/>    }<br/>   }<br/>   else<br/>    $this-&gt;missingAction($actionID);<br/>}</p> <p>Yii::app()-&gt;beforeControllerAction() 实际是固定返回true的,所以action对象实际是通过控制器的runActionWithFilters()被run的</p> <p>public function runActionWithFilters($action,$filters)<br/>{<br/>   // 控制器里没有设置过滤器<br/>   if(empty($filters))<br/>    $this-&gt;runAction($action);<br/>   else<br/>   {<br/>    // 创建过滤器链对象并运行<br/>    $priorAction=$this-&gt;_action;<br/>    $this-&gt;_action=$action;<br/>    CFilterChain::create($this,$action,$filters)-&gt;run();<br/>    $this-&gt;_action=$priorAction;<br/>   }<br/>}</p> <p>没有过滤器,runAction()就是最终要调用前面创建的action对象的run()方法:</p> <p>public function runAction($action)<br/>{<br/>   $priorAction=$this-&gt;_action;<br/>   $this-&gt;_action=$action;<br/>   if($this-&gt;beforeAction($action))<br/>   {<br/>    $action-&gt;run();<br/>    $this-&gt;afterAction($action);<br/>   }<br/>   $this-&gt;_action=$priorAction;<br/>}</p> <p>每个filter都要实现IFilter接口,filter实现的preFilter()方法在$action-&gt;run()之前调用,如果判断action可以执行则返回true,否则返回false</p> <p>if($filter1-&gt;preFilter())<br/>if($filter2-&gt;preFilter())<br/>   if($filtern-&gt;preFilter())<br/>    $action-&gt;run()<br/>    $filtern-&gt;postFilter()<br/>   $filter2-&gt;postFilter()<br/>$filter1-&gt;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-&gt;getViewFile($view))!==false)<br/>   {<br/>    $output=$this-&gt;renderFile($viewFile,$data,true);<br/>    if($processOutput)<br/>     $output=$this-&gt;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}'=&gt;get_class($this), '{view}'=&gt;$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-&gt;_widgetStack);<br/>   // 如果配置了其他的ViewRenderer<br/>   if(($renderer=Yii::app()-&gt;getViewRenderer())!==null)<br/>    $content=$renderer-&gt;renderFile($this,$viewFile,$data,$return);<br/>   else<br/>    // yii 自身的render<br/>    $content=$this-&gt;renderInternal($viewFile,$data,$return);<br/>   if(count($this-&gt;_widgetStack)===$widgetCount)<br/>    return $content;<br/>   else<br/>   {<br/>    $widget=end($this-&gt;_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}'=&gt;get_class($this), '{view}'=&gt;$viewFile, '{widget}'=&gt;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-&gt;renderPartial($view,$data,true);<br/>   if(($layoutFile=$this-&gt;getLayoutFile($this-&gt;layout))!==false)<br/>    $output=$this-&gt;renderFile($layoutFile,array('content'=&gt;$output),true);</p> <p>   $output=$this-&gt;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()-&gt;getClientScript()-&gt;render($output);</p> <p>   // if using page caching, we should delay dynamic output replacement<br/>   if($this-&gt;_dynamicOutput!==null &amp;&amp; $this-&gt;isCachingStackEmpty())<br/>    $output=$this-&gt;processDynamicOutput($output);</p> <p>   if($this-&gt;_pageStates===null)<br/>    $this-&gt;_pageStates=$this-&gt;loadPageStates();<br/>   if(!empty($this-&gt;_pageStates))<br/>    $this-&gt;savePageStates($this-&gt;_pageStates,$output);</p> <p>   return $output;<br/>}</p> <p/></div></td></tr></tbody></table>