您的位置:首页 > 编程语言 > PHP开发

Yii PHP 框架分析(四)

2014-04-30 14:46 483 查看
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->processRequest();

$this->onEndRequest(new CEvent($this));

}

processRequest()开始处理请求,由CWebApplication实现:

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);

}

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(empty($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(!empty($this->_pageStates))

$this->savePageStates($this->_pageStates,$output);

return $output;

}

#Web
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: