- Yii PHP 框架分析(四)
- 作者:wdy
- http://hi.baidu.com/delphiss/blog/item/c15b314f05f9dfc0d0c86a26.html
- Yii應用的入口腳本最後一句啓動了WebApplication
- Yii::createWebApplication($config)->run();
- CApplication:
- public function run()
- {
- $this->onBeginRequest(new CEvent($this));
- $this->proce***equest();
- $this->onEndRequest(new CEvent($this));
- }
- proce***equest()開始處理請求,由CWebApplication實現:
- public function proce***equest()
- {
- 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);
- }
- urlManager應用組件的parseUrl() 創建了$route (形式爲controllerID/actionID的字符串),runController()創建Controller對象開始處理http請求。
- $route 的值可能存在以下幾種情況:
- - 爲空: 用 defaultController 值代替;
- - “moduleID/controllerID/actionID”: module下的
- - “controllerID/actionID” : 最常見的形式
- - “folder1/folder2/controllerID/actionID” 多級目錄下的控制器
- runController首先調用createController()創建控制器對象
- public function createController($route,$owner=null)
- {
- // $owner爲空則設置爲$this,即 $_app對象
- if($owner===null)
- $owner=$this;
- // $route爲空設置爲defaultController,在$config裏配置
- if(($route=trim($route,'/'))==='')
- $route=$owner->defaultController;
- $caseSensitive=$this->getUrlManager()->caseSensitive;
- $route.='/';
- // 逐一取出 $route 按 ‘/’分割後的第一段進行處理
- while(($pos=strpos($route,'/'))!==false)
- {
- // $id 裏存放的是 $route 第一個 ‘/’前的部分
- $id=substr($route,0,$pos);
- if(!preg_match('/^\w+$/',$id))
- return null;
- if(!$caseSensitive)
- $id=strtolower($id);
- // $route 存放’/’後面部分
- $route=(string)substr($route,$pos+1);
- if(!isset($basePath)) // 完整$route的第一段
- {
- // 如果$id在controllerMap[]裏做了映射
- // 直接根據$id創建controller對象
- if(isset($owner->controllerMap[$id]))
- {
- return array(
- Yii::createComponent($owner->controllerMap[$id],$id,$owner===$this?null:$owner),
- $this->parseActionParams($route),
- );
- }
- // $id 是系統已定義的 module,根據$id取得module對象作爲$owner參數來createController
- if(($module=$owner->getModule($id))!==null)
- return $this->createController($route,$module);
- // 控制器所在的目錄
- $basePath=$owner->getControllerPath();
- $controllerID='';
- }
- else
- $controllerID.='/';
- $className=ucfirst($id).'Controller';
- $classFile=$basePath.DIRECTORY_SEPARATOR.$className.'.php';
- // 控制器類文件存在,則require並創建控制器對象&返回
- if(is_file($classFile))
- {
- if(!class_exists($className,false))
- require($classFile);
- if(class_exists($className,false) && is_subclass_of($className,'CController'))
- {
- $id[0]=strtolower($id[0]);
- return array(
- new $className($controllerID.$id,$owner===$this?null:$owner),
- $this->parseActionParams($route),
- );
- }
- return null;
- }
- // 未找到控制器類文件,可能是多級目錄,繼續往子目錄搜索
- $controllerID.=$id;
- $basePath.=DIRECTORY_SEPARATOR.$id;
- }
- }
- createController() 返回一個創建好的控制器對象和actionID, runController()調用控制器的init()方法和run($actionID)來運行控制器:
- 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)));
- }
- $controller->init()裏沒有動作, run():
- public function run($actionID)
- {
- if(($action=$this->createAction($actionID))!==null)
- {
- if(($parent=$this->getModule())===null)
- $parent=Yii::app();
- if($parent->beforeControllerAction($this,$action))
- {
- $this->runActionWithFilters($action,$this->filters());
- $parent->afterControllerAction($this,$action);
- }
- }
- else
- $this->missingAction($actionID);
- }
- $controller->run($actionID)裏首先創建了 Action對象:
- public function createAction($actionID)
- {
- // 爲空設置爲defaultAction
- if($actionID==='')
- $actionID=$this->defaultAction;
- // 控制器裏存在 'action'.$actionID 的方法,創建CInlineAction對象
- if(method_exists($this,'action'.$actionID) && strcasecmp($actionID,'s')) // we have actions method
- return new CInlineAction($this,$actionID);
- // 否則根據actions映射來創建Action對象
- else
- return $this->createActionFromMap($this->actions(),$actionID,$actionID);
- }
- 這裏可以看到控制器並不是直接調用了action方法,而是需要一個Action對象來運行控制器動作,這樣就統一了控制器方法和actions映射的action對象對action的處理,即兩種形式的action處理都統一爲 IAction接口的run()調用。
- IAction接口要求實現run(),getId(),getController () 三個方法,Yii提供的CAction類要求構造函數提供Controller和Id並實現了getId()和getController ()的處理,Action類從CAction繼承即可。
- CInlineAction在web/action下,run()是很簡單的處理過程,調用了Controller的action方法:
- class CInlineAction extends CAction
- {
- public function run()
- {
- $method='action'.$this->getId();
- $this->getController()->$method();
- }
- }
- 回到 $controller->run($actionID)
- public function run($actionID)
- {
- if(($action=$this->createAction($actionID))!==null)
- {
- if(($parent=$this->getModule())===null)
- $parent=Yii::app();
- if($parent->beforeControllerAction($this,$action))
- {
- $this->runActionWithFilters($action,$this->filters());
- $parent->afterControllerAction($this,$action);
- }
- }
- else
- $this->missingAction($actionID);
- }
- Yii::app()->beforeControllerAction() 實際是固定返回true的,所以action對象實際是通過控制器的runActionWithFilters()被run的
- public function runActionWithFilters($action,$filters)
- {
- // 控制器裏沒有設置過濾器
- if(emptyempty($filters))
- $this->runAction($action);
- else
- {
- // 創建過濾器鏈對象並運行
- $priorAction=$this->_action;
- $this->_action=$action;
- CFilterChain::create($this,$action,$filters)->run();
- $this->_action=$priorAction;
- }
- }
- 沒有過濾器,runAction()就是最終要調用前面創建的action對象的 run()方法:
- public function runAction($action)
- {
- $priorAction=$this->_action;
- $this->_action=$action;
- if($this->beforeAction($action))
- {
- $action->run();
- $this->afterAction($action);
- }
- $this->_action=$priorAction;
- }
- 每個filter都要實現IFilter接口,filter實現的 preFilter()方法在$action->run()之前調用,如果判斷action可以執行則返回true,否則返回false
- if($filter1->preFilter())
- if($filter2->preFilter())
- if($filtern->preFilter())
- $action->run()
- $filtern->postFilter()
- $filter2->postFilter()
- $filter1->postFilter()
- 在action裏最常見的操作就是render view文件: renderPartial()和render()。render()在處理view文件後會把結果放入layout文件內。
- public function renderPartial($view,$data=null,$return=false,$processOutput=false)
- {
- if(($viewFile=$this->getViewFile($view))!==false)
- {
- $output=$this->renderFile($viewFile,$data,true);
- if($processOutput)
- $output=$this->processOutput($output);
- if($return)
- return $output;
- else
- echo $output;
- }
- else
- throw new CException(Yii::t('yii','{controller} cannot find the requested view "{view}".',
- array('{controller}'=>get_class($this), '{view}'=>$view)));
- }
- getViewFile($view)獲得$view的完整路徑:
- $view 以 ‘/’開頭的,以系統views目錄作爲起始目錄+$view+.php
- $view含有別名的,查找別名的真實路徑
- 其他的以modele view目錄作爲起始目錄+$view+.php
- 如果沒有在$config裏配置第三方的renderer,renderFile() 裏實際是調用了yii自身提供的renderInternal()來render view文件:
- public function renderFile($viewFile,$data=null,$return=false)
- {
- $widgetCount=count($this->_widgetStack);
- // 如果配置了其他的ViewRenderer
- if(($renderer=Yii::app()->getViewRenderer())!==null)
- $content=$renderer->renderFile($this,$viewFile,$data,$return);
- else
- // yii 自身的render
- $content=$this->renderInternal($viewFile,$data,$return);
- if(count($this->_widgetStack)===$widgetCount)
- return $content;
- else
- {
- $widget=end($this->_widgetStack);
- 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.',
- array('{controller}'=>get_class($this), '{view}'=>$viewFile, '{widget}'=>get_class($widget))));
- }
- }
- Yii的renderer用的是php本身作爲模板系統:
- public function renderInternal($_viewFile_,$_data_=null,$_return_=false)
- {
- // extract函數將$_data_從數組中將變量導入到當前的符號表
- if(is_array($_data_))
- extract($_data_,EXTR_PREFIX_SAME,'data');
- else
- $data=$_data_;
- if($_return_)
- {
- ob_start();
- ob_implicit_flush(false);
- require($_viewFile_);
- return ob_get_clean();
- }
- else
- require($_viewFile_);
- }
- render()的實際上是先renderPartial view文件,然後renderFile layoutfile,並將view文件的結果做爲$content變量傳入。
- public function render($view,$data=null,$return=false)
- {
- $output=$this->renderPartial($view,$data,true);
- if(($layoutFile=$this->getLayoutFile($this->layout))!==false)
- $output=$this->renderFile($layoutFile,array('content'=>$output),true);
- $output=$this->processOutput($output);
- if($return)
- return $output;
- else
- echo $output;
- }
- processOutput將render的結果再做處理,比如在head加上css 或js腳本等。
- public function processOutput ($output)
- {
- Yii::app()->getClientScript()->render($output);
- // if using page caching, we should delay dynamic output replacement
- if($this->_dynamicOutput!==null && $this->isCachingStackEmpty())
- $output=$this->processDynamicOutput($output);
- if($this->_pageStates===null)
- $this->_pageStates=$this->loadPageStates();
- if(!emptyempty($this->_pageStates))
- $this->savePageStates($this->_pageStates,$output);
- return $output;
- }