作者: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->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 PHP 框架分析(四)的更多相关文章

  1. Yii PHP 框架分析(二)

    Yii PHP 框架分析(二)作者:wdy http://hi.baidu.com/delphiss/blog/item/54597af595085ad3f3d38552.html Yii是基于组件( ...

  2. Yii PHP 框架分析 (一)

    Yii PHP 框架分析 (一)作者:wdy http://hi.baidu.com/delphiss/blog/item/f7da86d787adb72506088b4b.html 基于yii1.0 ...

  3. Yii PHP 框架分析(三)

    作者:wdy http://hi.baidu.com/delphiss/blog/item/357663d152c0aa85a1ec9c44.html Yii应用的入口脚本引用出了Yii类,Yii类的 ...

  4. Flask框架 (四)—— 请求上下文源码分析、g对象、第三方插件(flask_session、flask_script、wtforms)、信号

    Flask框架 (四)—— 请求上下文源码分析.g对象.第三方插件(flask_session.flask_script.wtforms).信号 目录 请求上下文源码分析.g对象.第三方插件(flas ...

  5. JavaScript框架设计(四) 字符串选择器(选择器模块结束)

    JavaScript框架设计(四) 字符串选择器(选择器模块结束) 经过前面JavaScript框架设计(三) push兼容性和选择器上下文的铺垫,实现了在某一元素下寻找,现在终于进入了字符串选择器 ...

  6. 深入浅出 - Android系统移植与平台开发(十一) - Sensor HAL框架分析之一

    作者:唐老师,华清远见嵌入式学院讲师. 1. Sensor的概念 Sensor即传感器,在当前智能手机上大量存在:G-Sensor.LightsSensor. ProximitySensor.Temp ...

  7. 深入浅出 - Android系统移植与平台开发(八)- HAL Stub框架分析

    作者:唐老师,华清远见嵌入式学院讲师. 1. HAL Stub框架分析 HAL stub的框架比较简单,三个结构体.两个常量.一个函数,简称321架构,它的定义在:@hardware/libhardw ...

  8. openwrt: Makefile 框架分析

    openwrt: Makefile 框架分析 原文链接:blog.chinaunix.net/uid-26675482-id-4704952.html 本篇的主要目的是想通过分析Makefile,了解 ...

  9. VS2010/MFC编程入门之四(MFC应用程序框架分析)

    VS2010/MFC编程入门之四(MFC应用程序框架分析)-软件开发-鸡啄米 http://www.jizhuomi.com/software/145.html   上一讲鸡啄米讲的是VS2010应用 ...

随机推荐

  1. 对BFC的理解

    转载http://www.cnblogs.com/lhb25/p/inside-block-formatting-ontext.html 在解释 BFC 是什么之前,需要先介绍 Box.Formatt ...

  2. 常见排序算法(PHP实现)

    function InsertSort($arr){ $num = count($arr); for($i = 1; $i < $num; $i++){ $key = $arr[$i]; for ...

  3. Python 手册(一)

    Python 手册 Guido van Rossum Fred L. Drake,  Jr., editor PythonLabs Email: python-docs@python.org Rele ...

  4. raywenderlich-iOS设计模式Part 1/2【译】

    原文地址:http://www.raywenderlich.com/86477/introducing-ios-design-patterns-in-swift-part-1 Update 04/22 ...

  5. LDR伪指令与ADR伪指令的区别

    测试代码 ldr r0, _start adr r0, _start ldr r0, =_start nop mov pc, lr _start: nop 设置代码的起始地址为 0x0c008000, ...

  6. java 集合(二)

    1.练习题 如果输入的字符里有非英语字母的,不给于执行

  7. Xcode 证书生成、设置、应用

    Xcode 证书生成.设置.应用,与大家分享.如果按下面步骤还不能编译成功,我手把手教你. 开发环境: Mac OS lion 10.7.4 XCode 4.3.3 1         点击钥匙图标 ...

  8. android SurfaceView绘制 重新学习--控制动画移动

    直接上demo,图是自己切的,将就用吧.点击左右两边分别向左右移动. public class MySurfaceView extends SurfaceView implements Callbac ...

  9. JQuery replace 替换全部

    天在做写个程序时遇到需要替换的功能,可是一开始用jquery的replace时,发现只替换到第一个.最后没办法,只好用正则表达式来例如下面   re = new RegExp("{thisc ...

  10. HDU 4734

    数位dp题:也是我做的第一个数位dp的题目: 感觉数位dp的模板性很强啊,思想都差不太多! 有几个写的很好的参考资料: 推荐一下: 数位计数问题解法研究 浅谈数位类统计问题 我的代码: #includ ...