Yii2.0源码阅读-视图(View)渲染过程
之前的文章我们根据源码的分析,弄清了Yii如何处理一次请求,以及根据解析的路由如何调用控制器中的action,那接下来好奇的可能就是,我在控制器action中执行了return $this->render('index')
,那render这个方法是如何完成渲染视图文件的工作的?我们继续从源码入手。
1、找到视图文件
先看我们在controller/action中视图渲染的调用:
public function actionIndex()
{
//代码省略
return $this->render('index',[
'model'=>$model
]);
}
1.1、找到render方法
因为所有的控制器类都继承了yii\web\Controller
,最终找到render位于yii\web\Controller
的父类yii\base\Controller
,看定义:
//yii\base\Controller
public function render($view, $params = [])
{
$content = $this->getView()->render($view, $params, $this);
return $this->renderContent($content);
}
可以看到真正的渲染操作并非在controller中处理,而是在view对象中完成的。这里只负责调用:
- 把视图文件给我渲染好
- 把渲染好的视图文件放进我定义的布局文件
- 返回
1.2、获取视图对象
从render方法的设计可以看到Yii2.0中,控制器(C)和视图(V)是完全分离的,各司其职。看$this->getView()
:
//yii\base\Controller
public function getView()
{
if ($this->_view === null) {
$this->_view = Yii::$app->getView();
}
return $this->_view;
}
Yii::$app
,就是我们new Application
的时候保存的一个全局application变量,在构造方法中已经保存好了Yii::$app = $this;
,可以查看yii\base\Application __construct
,不过多描述了。
这样我们知道了Yii::$app->getView()
,getView应该位于yii\web\Application
,或者其父类中`:
//yii\base\Application
public function getView()
{
return $this->get('view');
}
get方法位于Application的父类yii\di\ServiceLocator
,根据源码可以看到,是对当前对象中的两个成员变量$_components
与$_definitions
做了判断,看是否有view这个组件的对象(实例),或者定义(['class'=>'yii\web\view']
),有实例则直接返回,否则根据定义创建实例(createObject)返回。
【注】关于$_definitions变量的初始化,其实在new Application的时候,在
yii\base\Application
的__construct
构造方法中,调用了preInit,而preInit方法中对核心组件(coreComponents)进行了初始化,这里就包括了'view' => ['class' => 'yii\web\View']
,组件的初始化就是这些配置信息保存的过程,主要通过yii\base\Object
中的构造方法,调用了Yii::configure($config)
,configure的过程就是对$_definitions $_components
赋值的过程。
最终经过getView()
,我们得到的就是一个yii\web\View
object
2、渲染视图文件
进入到yii\web\View
中的render方法,位于其父类。
//yii\base\View
public function render($view, $params = [], $context = null)
{
$viewFile = $this->findViewFile($view, $context);
return $this->renderFile($viewFile, $params, $context);
}
通过代码可以看到,渲染视图文件的第一步就是找到这个视图文件在磁盘中的文件存储位置(真实路径)。从findViewFile方法寻找路径的过程,也可看出我们的$view
参数支持的几种格式。
2.1、$view
参数格式
源码不放了,大家对照yii\base\View
中的function findViewFile,或看这里
- 按照alias别名的方式
检查$view
中是否包含“@”,如果有说明是使用了别名的方法,直接通过Yii::getAlias($view)
获取,因为形式是"@backend/views/site/index"这种格式,而在config文件夹下的bootstrap.php中已经对backend进行了setAlias,所以通过getAias简单处理就可返回真实路径了 - 以"//"开头的
$view
,如"//site/index"
匹配到这种形式执行Yii::$app->getViewPath()
,getViewPath方法事实上是:获取我们在配置文件中定义的basePath,再根据Yii2.0中约定的目录结构,加上"/views"返回 - 以"/"开头
相当于绝对路径的方式,在当前控制器所在的module中进行寻找,这个controller对象的module成员保存的是当前控制器所在的module对象,这个保存是在createControllerByID中调用Yii::createObject()的时候进行赋值的。yii\base\Controller
中的构造方法有一句:$this->module = $module;
- controller对象调用View进行视图渲染时,将对象自身传递给了view,也就是$context,如果这个控制器实现了ViewContextInterface接口(接口中就定义了一个getViewPath方法),那么直接调用控制器的getViewPath
- 最后一种情况就是根据当前视图文件的文件路径来返回视图路径(这种应该是在视图文件中渲染其他视图的情况)
【注】2与3其实都是调用
yii\base\Module
中的getViewPath,getViewPath中调用了getBasePath,区别在于2中用的是Yii::$app 这个对象,Application对象初始化的时候在preInit中已经对将配置文件中的basePath初始化并保存在了$_basePath
中;3中getViewPath调用的发起者是controller实例所属的module对象,module对象创建的时候并未初始化$_basePath
,所以可以看到getBasePaht中是使用反射的方法获取当前module对象对应文件的路径,进而返回basePath的
2.2、renderFile
renderFile的操作就比较简单了,根据视图文件的后缀来判断是否启用了相应的模板引擎,如Smarty、Twig等,如果都没有那就默认的php文件的方式(renderPhpFile)进行渲染,renderPhpFile的代码非常简单:
public function renderPhpFile($_file_, $_params_ = [])
{
ob_start();
ob_implicit_flush(false);
//将我们render的参数数组extract为本地变量
extract($_params_, EXTR_OVERWRITE);
//require 我们的视图文件
require($_file_);
//清空并返回当前缓冲区的内容
return ob_get_clean();
}
经过以上的分析,一个视图文件的渲染就完成了,而我们的Yii框架中大多使用了布局文件。所以回到yii\base\Controller
的render方法中,视图文件渲染之后返回的是一个字符串,保存在了$content中,然后执行了return $this->renderContent($content)
,这其实就是把视图文件渲染后的结果放到布局中,这也是为什么我们的布局文件中会有这么一行代码:<?=$content?>
。renderContent其实就是获取布局文件的路径然后在调用View中的renderFile方法,过程跟视图渲染的过程一样。
至此,Yii2.0视图渲染过程分析完毕。
【附】render渲染代码调用流程