YII 的源码分析(二)
上一篇简单分析了一下yii的流程,从创建一个应用,到屏幕上输出结果。这一次我来一个稍复杂一点的,重点在输出上,不再是简单的一行"hello world",而是要经过view(视图)层的处理。
依然是demos目录,这次我们选择hangman,一个简单的猜字游戏。
和helloworld应用相比,这次多了main.php,打开main看下源码:
<?php return array(
'name'=>'Hangman Game',
'defaultController'=>'game',
'components'=>array(
'urlManager'=>array(
'urlFormat'=>'path',
'rules'=>array(
'game/guess/<g:\w>'=>'game/guess',
),
),
),
);
在我们以后的实际项目中,也是经常要用到配置文件的,所以我觉得有必要了解一下yii的配置文件--main.php
'name'=>'这里通常是定义网站的标题',也就是我们打开index.php时,在网页上显示的标题。
'defaultController'=>'这里是默认的控制器',也就是我们的index.php后面没有指定控制器时系统采用的控制器,如果我们这里没有指出来,默认就是site
'components'=>'这里是组件的参数,用多维数组进行配置。' 具体的参数可以查看yii手册。
Yii::createWebApplication($config)->run(); 上一次我们已经详细分析过它了,这里就不再走一遍了。
上次我们没有配置过程,所以$this->configure($config)什么也没有做,但是这次有配置参数,所以我们进去看看yii做了哪些操作:
CApplication自己没有实现configure方法,是继承于CModule.php的:
public function configure($config)
{
if(is_array($config))
{
foreach($config as $key=>$value)
$this->$key=$value;
}
}
代码非常简单,就是把配置参数的键做为类的属性名,value做为类的属性值进行了扩展。
由于url是index.php,后面没有任何参数,所以都是走的默认控制器,也就是我们在main.php中设定的game. 所以$controller 就等于 controllers/gameController.php, 通过上次的源码分析我们可以知道,在gameController.php中没有init方法时,都是走的父类中定义的默认方法(实际上是一个空方法),
$controller->run($actionID); == gameController->run('');
前面已经分析过了,没有指定时,都是默认参数。那么此时的$actionID为空,actionID就是gameController中定义的默认动作:public $defaultAction='play';
runActionWithFilters ---> runAction --> $action->runWithParams
走了这么多过程,和hello world的流程是差不多的。据上次的分析可以知道,这里执行了
$controller->$methodName(); 也就是GameController->actionPlay() 到此,我们本节的重点才真正开始:
public function actionPlay()
{
static $levels=array(
'10'=>'Easy game; you are allowed 10 misses.',
'5'=>'Medium game; you are allowed 5 misses.',
'3'=>'Hard game; you are allowed 3 misses.',
); // if a difficulty level is correctly chosen
if(isset($_POST['level']) && isset($levels[$_POST['level']]))
{
$this->word=$this->generateWord();
$this->guessWord=str_repeat('_',strlen($this->word));
$this->level=$_POST['level'];
$this->misses=0;
$this->setPageState('guessed',null);
// show the guess page
$this->render('guess');
}
else
{
$params=array(
'levels'=>$levels,
// if this is a POST request, it means the level is not chosen
'error'=>Yii::app()->request->isPostRequest,
);
// show the difficulty level page
$this->render('play',$params);
}
}
重点请看 $this->render('play',$params); 这个render方法这么面熟,很多框架中都有类似的方法,比如discuz,smarty,CI 等等. 纵观yii框架,render 在它整个MVC模式中,是V得以实现的重要骨干。所以有必要把它翻个底朝天。
public function render($view,$data=null,$return=false)
{
if($this->beforeRender($view))
{
$output=$this->renderPartial($view,$data,true);
if(($layoutFile=$this->getLayoutFile($this->layout))!==false)
$output=$this->renderFile($layoutFile,array('content'=>$output),true); $this->afterRender($view,$output); $output=$this->processOutput($output); if($return)
return $output;
else
echo $output;
}
}
先看$output=$this->renderPartial($view,$data,true); 从字面上来看,就是渲染局部视图。
public function renderPartial($view,$data=null,$return=false,$processOutput=false)
{ if(($viewFile=$this->getViewFile($view))!==false)
{
//$viewFile=yii\demos\hangman\protected\views\game\play.php
$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)));
}
这里调用了renderFile来处理,这个renderfile哪里来的,要自己学会分析继承关系,不明白的看第一篇,我这里直接上源码了:
public function renderFile($viewFile,$data=null,$return=false)
{
$widgetCount=count($this->_widgetStack);
if(($renderer=Yii::app()->getViewRenderer())!==null && $renderer->fileExtension==='.'.CFileHelper::getExtension($viewFile)){
$content=$renderer->renderFile($this,$viewFile,$data,$return);
}
else{
$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))));
}
}
逻辑上走的是renderInternal方法:
public function renderInternal($_viewFile_,$_data_=null,$_return_=false)
{
// we use special variable names here to avoid conflict when extracting 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_);
}
为什么要用ob_start() ? 这个方法比较有意思。它非常巧妙的把play.php视图中的html的内容和php输出的内容组装在了一起。然后整个return 出去,非常值得借鉴的思想。
接着,我们再回到render。走余下的过程。
$output=$this->renderFile($layoutFile,array('content'=>$output),true);
这里直接又调用了一次renderFile方法,这个$layoutFile是什么呢?就是yii\demos\hangman\protected\views\layouts\main.php
通过上次的分析,$output就是main.php的内容了,不过呢,是结合了play.php的内容。这又是怎么做到的呢?
这就是extract($_data_,EXTR_PREFIX_SAME,'data');所发挥的功能了。
还有一个processOutput方法,输出前进行一些缓存什么的,这里先不深究它。
最后就是echo $output;因此屏幕上就有内容显示出来了。
也就是我们在index.php上最终看到的内容了。本次渲染比较简单,但是该走的逻辑都差不多用到了。
总结一下:视图层,是通过render方法进行渲染输出的。而render实际上是分成两部分来做的,一部分是局部视图,另一部分是通用视图。
类似于如下结构:
<html><!--这里是通用视图-->
<head>
<title>xxx</title>
</head>
<body>
{{这里是局部视图内容}}
</body>
</html>
render输出的是两者融合的内容。这样做的好处也是显而易见的,避免创建重复内容。
YII 的源码分析(二)的更多相关文章
- Fresco 源码分析(二) Fresco客户端与服务端交互(1) 解决遗留的Q1问题
4.2 Fresco客户端与服务端的交互(一) 解决Q1问题 从这篇博客开始,我们开始讨论客户端与服务端是如何交互的,这个交互的入口,我们从Q1问题入手(博客按照这样的问题入手,是因为当时我也是从这里 ...
- YII框架源码分析(百度PHP大牛创作-原版-无广告无水印)
YII 框架源码分析 百度联盟事业部——黄银锋 目 录 1. 引言 3 1.1.Yii 简介 3 1.2.本文内容与结构 3 2.组件化与模块化 4 2.1.框架加载和运行流程 4 ...
- 框架-springmvc源码分析(二)
框架-springmvc源码分析(二) 参考: http://www.cnblogs.com/leftthen/p/5207787.html http://www.cnblogs.com/leftth ...
- Tomcat源码分析二:先看看Tomcat的整体架构
Tomcat源码分析二:先看看Tomcat的整体架构 Tomcat架构图 我们先来看一张比较经典的Tomcat架构图: 从这张图中,我们可以看出Tomcat中含有Server.Service.Conn ...
- 十、Spring之BeanFactory源码分析(二)
Spring之BeanFactory源码分析(二) 前言 在前面我们简单的分析了BeanFactory的结构,ListableBeanFactory,HierarchicalBeanFactory,A ...
- Vue源码分析(二) : Vue实例挂载
Vue源码分析(二) : Vue实例挂载 author: @TiffanysBear 实例挂载主要是 $mount 方法的实现,在 src/platforms/web/entry-runtime-wi ...
- 多线程之美8一 AbstractQueuedSynchronizer源码分析<二>
目录 AQS的源码分析 该篇主要分析AQS的ConditionObject,是AQS的内部类,实现等待通知机制. 1.条件队列 条件队列与AQS中的同步队列有所不同,结构图如下: 两者区别: 1.链表 ...
- YII 的源码分析(-)
做为源码分析的首秀,我就挑了yii(读作歪依依而不是歪爱爱):它的赞美之词我就不多说了,直接入正题.先准备材料,建议直从官网下载yii的源码包(1.1.15). 在demos里边有一个最简单的应用—h ...
- ABP源码分析二:ABP中配置的注册和初始化
一般来说,ASP.NET Web应用程序的第一个执行的方法是Global.asax下定义的Start方法.执行这个方法前HttpApplication 实例必须存在,也就是说其构造函数的执行必然是完成 ...
随机推荐
- 浅谈MySql的存储引擎(表类型)
来源:http://www.cnblogs.com/lina1006/archive/2011/04/29/2032894.html 什么是MySql数据库 通常意义上,数据库也就是数据的集合,具体到 ...
- gulp-babel使用
各大浏览器厂商对es2015功能支持不完全,等到全部支持会等很长时间,如果现在使用es2015,可以选择babel一个将ES6/ES7写的代码转换为ES5代码的编译器. 我们选择使用gulp自动化编译 ...
- HTML 上传图片实用小技巧
最近写的项目需要用的上传图片的功能但是浏览器自带的按钮样式实在是不忍直视,肯定要进行修改,网上也有很多方法(自己查....),我这里用了个取巧的方法:就是函数的间接调用 在点击btn的时候让它执行了图 ...
- 四、jquery中的事件与应用
当用户浏览页面时,浏览器会对页面代码进行解释或编译--这个过程实质上是通过时间来驱动的,即页面在加载时,执行一个Load事件,在这个事件中实现浏览器编译页面代码的过程.时间无论在页面元素本身还是在元素 ...
- JavaScript资源大全中文版(Awesome最新版--转载自张果老师博客)
JavaScript资源大全中文版(Awesome最新版) 目录 前端MVC 框架和库 包管理器 加载器 打包工具 测试框架 框架 断言 覆盖率 运行器 QA 工具 基于 Node 的 CMS 框 ...
- Java中堆内存和栈内存详解
Java把内存分成两种,一种叫做栈内存,一种叫做堆内存 在函数中定义的一些基本类型的变量和对象的引用变量都是在函数的栈内存中分配.当在一段代码块中定义一个变量时,java就在栈中为这个变量分配内存空间 ...
- ★★★Oracle sql 传参特别注意★★★
最近遇到一个非常烦人的问题,用传参的方式执行sql语句结果老是报 Oracle ORA-01722: 无效数字 一直无法找到原因. 表结构大致如下: table test_station ( tblR ...
- ASP.Net WebForm温故知新学习笔记:一、aspx与服务器控件探秘
开篇:毫无疑问,ASP.Net WebForm是微软推出的一个跨时代的Web开发模式,它将WinForm开发模式的快捷便利的优点移植到了Web开发上,我们只要学会三步:拖控件→设属性→绑事件,便可以行 ...
- 一个App完成入门篇(六)- 完成通讯录页面
第五章和第六章间隔时间有点长,对不起大家了.下面继续. 本节教程将要教会大家如何加载本地通讯录. 导入项目 导入通讯录 自定义js模块 发送和订阅page消息 将要学习的demo效果图如下所示 1. ...
- String驻留带来的危害
原创文章转载请注明出处:@协思, http://zeeman.cnblogs.com 前段时间接手了一个项目优化工作,在同等场景下内存使用从4G降低到200M. 项目是将实体序列化成字符串存入 ...