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;
}
以上就是yii框架分析(四)——webapplication的run函数详细解析的内容。