慢悠悠地Yii框架源码阅读(3) [ 2.0 版本 ]
Hello,大家好,大帅又来了,各位,来了请点赞。
上一篇文章梳理了整个框架的大致流程,介绍了页面通过Module入口找到了controller,另外有通过controller的render()函数找到了相应的layout和view页面。但是如何找到的没有介绍,只是简单的描述了一下,本篇文章将详细介绍一下页面渲染的过程。想必各位都用Yii框架的时候,都会在controller里面写类似于render的函数,render的意思是渲染,那么渲染的类型有很多,看了源码之后,\base\Controller.php里面有
public function render($view, $params = []);
public function renderContent($content);
public function renderPartial($view, $params = []);
public function renderFile($file, $params = []);
这四种类型,各位在用的时候可以各取所需。我首先以render为入口来进行分析。
public function render($view, $params = [])
{
$content = $this->getView()->render($view, $params, $this);
return $this->renderContent($content);
}
在这个函数中,首先看参数,render函数接收两个参数,第一个参数描述的是要加载view文件,第二个参数是传给view文件的一些参数。接着往下看,找到getView()函数。
public function getView()
{
if ($this->_view === null) {
$this->_view = Yii::$app->getView();
}
return $this->_view;
}
也是通过Yii::$app->getView();函数获得view对象(如果不知道怎么获得,可以参考前两篇文章里的getRequest, getResponse的解释),然后通过view对象的render函数加载进去,注意getView()->render($view, $params, $this);
中那个$this参数,这个$this传递过去的类型其实是controller对象,这个之后在view对象中会遇到,请先记住。另外$view参数指的是你自己代码中要加载的view文件。然后找到view类,指向的是base\View.php,
public function render($view, $params = [], $context = null)
{
$viewFile = $this->findViewFile($view, $context);
return $this->renderFile($viewFile, $params, $context);
}
先看参数,没错这里的context就是指向的是controller,这下你就可以联想到如何在view文件或者layout文件中获得controller里面某个变量的值,是通过$this->context->XXX获得的,原理就在这里。看代码,第一步,是findViewFile($view, $context);看名字就知道是要找到view文件,那么我们找到这个函数,
protected function findViewFile($view, $context = null)
{
if (strncmp($view, '@', 1) === 0) {
// e.g. "@app/views/main"
$file = Yii::getAlias($view);
} elseif (strncmp($view, '//', 2) === 0) {
// e.g. "//layouts/main"
$file = Yii::$app->getViewPath() . DIRECTORY_SEPARATOR . ltrim($view, '/');
} elseif (strncmp($view, '/', 1) === 0) {
// e.g. "/site/index"
if (Yii::$app->controller !== null) {
$file = Yii::$app->controller->module->getViewPath() . DIRECTORY_SEPARATOR . ltrim($view, '/');
} else {
throw new InvalidCallException("Unable to locate view file for view '$view': no active controller.");
}
} elseif ($context instanceof ViewContextInterface) {
$file = $context->getViewPath() . DIRECTORY_SEPARATOR . $view;
} elseif (($currentViewFile = $this->getViewFile()) !== false) {
$file = dirname($currentViewFile) . DIRECTORY_SEPARATOR . $view;
} else {
throw new InvalidCallException("Unable to resolve view file for view '$view': no active view context.");
}
if (pathinfo($file, PATHINFO_EXTENSION) !== '') {
return $file;
}
$path = $file . '.' . $this->defaultExtension;
if ($this->defaultExtension !== 'php' && !is_file($path)) {
$path = $file . '.php';
}
return $path;
}
这几个if语句中,都有实例注释,可以脑补一下自己写的代码中render函数里第一个参数是什么样子的,然后可以通过这个代码查看,这里默认分析$view = 'index'的流程,直接进入到
elseif ($context instanceof ViewContextInterface)
这里,$file = $context->getViewPath() . DIRECTORY_SEPARATOR . $view;
代码中这个$context在前文中说了指的是controller,那么回到controller里找到getViewPath函数,
public function getViewPath()
{
if ($this->_viewPath === null) {
$this->_viewPath = $this->module->getViewPath() . DIRECTORY_SEPARATOR . $this->id;
}
return $this->_viewPath;
}
可以看出在controller里面是通过module函数进行getViewPath()查找的,而这里的module指向的是app,而app又继承于Module.php,通过查看Module.php里面的getViewPath()函数可以看到,
public function getViewPath()
{
if ($this->_viewPath === null) {
$this->_viewPath = $this->getBasePath() . DIRECTORY_SEPARATOR . 'views';
}
return $this->_viewPath;
}
可以看到,就是指的是项目工程里的views文件夹。回到base\View.php继续往下走,最后renturn的就是这个文件,并默认以.php为后缀。返回到render函数继续往下走,return $this->renderFile($viewFile, $params, $context);
找到renderFile()函数,
public function renderFile($viewFile, $params = [], $context = null)
{
$viewFile = Yii::getAlias($viewFile);
if ($this->theme !== null) {
$viewFile = $this->theme->applyTo($viewFile);
}
if (is_file($viewFile)) {
$viewFile = FileHelper::localize($viewFile);
} else {
throw new InvalidParamException("The view file does not exist: $viewFile");
}
$oldContext = $this->context;
if ($context !== null) {
$this->context = $context;
}
$output = '';
$this->_viewFiles[] = $viewFile;
if ($this->beforeRender($viewFile, $params)) {
Yii::trace("Rendering view file: $viewFile", __METHOD__);
$ext = pathinfo($viewFile, PATHINFO_EXTENSION);
if (isset($this->renderers[$ext])) {
if (is_array($this->renderers[$ext]) || is_string($this->renderers[$ext])) {
$this->renderers[$ext] = Yii::createObject($this->renderers[$ext]);
}
/* @var $renderer ViewRenderer */
$renderer = $this->renderers[$ext];
$output = $renderer->render($this, $viewFile, $params);
} else {
$output = $this->renderPhpFile($viewFile, $params);
}
$this->afterRender($viewFile, $params, $output);
}
array_pop($this->_viewFiles);
$this->context = $oldContext;
return $output;
}
在这段代码里,前面无非就是进行一些beforeRender处理,没什么好分析的,直接看$output = $this->renderPhpFile($viewFile, $params);找到renderPhpFile函数,
public function renderPhpFile($_file_, $_params_ = [])
{
ob_start();
ob_implicit_flush(false);
extract($_params_, EXTR_OVERWRITE);
require($_file_);
return ob_get_clean();
}
这里面关系到一系列的缓冲区函数,我也不太明白,大概意思就是控制输出的。然后将view文件包含进来了。然后一路返回到base/controller.php的render函数,这样的话,
public function render($view, $params = [])
{
$content = $this->getView()->render($view, $params, $this);
return $this->renderContent($content);
}
$content返回的就是view文件内容,总的来看view.php文件里的render函数和renderFile函数的区别就在于,view里面的render函数是先找到view文件的位置,在调用renderfile函数进行加载,等于说比renderFile多了一层查找文件路径的过程。
接下来就要return了,但是要进行renderContent($content)函数,注意参数是将view文件的内容作为了参数传递进行了。
找到renderContent函数。
public function renderContent($content)
{
$layoutFile = $this->findLayoutFile($this->getView());
if ($layoutFile !== false) {
return $this->getView()->renderFile($layoutFile, ['content' => $content], $this);
} else {
return $content;
}
}
第一句应该会猜到,就是找到layout文件的路径。
找到findLayoutFile函数,注意参数传递。
public function findLayoutFile($view)
{
$module = $this->module;
if (is_string($this->layout)) {
$layout = $this->layout;
} elseif ($this->layout === null) {
while ($module !== null && $module->layout === null) {
$module = $module->module;
}
if ($module !== null && is_string($module->layout)) {
$layout = $module->layout;
}
}
if (!isset($layout)) {
return false;
}
if (strncmp($layout, '@', 1) === 0) {
$file = Yii::getAlias($layout);
} elseif (strncmp($layout, '/', 1) === 0) {
$file = Yii::$app->getLayoutPath() . DIRECTORY_SEPARATOR . substr($layout, 1);
} else {
$file = $module->getLayoutPath() . DIRECTORY_SEPARATOR . $layout;
}
if (pathinfo($file, PATHINFO_EXTENSION) !== '') {
return $file;
}
$path = $file . '.' . $view->defaultExtension;
if ($view->defaultExtension !== 'php' && !is_file($path)) {
$path = $file . '.php';
}
return $path;
}
这个文件的内容跟getViewPath函数的内容有点类似,显示通过配置找到用户是否定义了layout文件,如果没有一切按照默认的来进行,就不做详细分析了。之后回到renderContent函数继续往下走,紧接着还是getView()->renderFile(),也是按照直接view文件查找的模式走了一遍,其实就可以把layout文件当成view文件的逻辑来进行。
通过这些就可以发现,框架是先加载view文件,再去加载layout文件的。
另外由此也可以解释,为什么render可以任意嵌套的,那是因为都只是单独通过$this触发的,相互之间不影响。
由此render的内容全部执行完毕,之后返回给controller作为一个output,接下来的事情就是上一篇文章中的内容了,将该篇内容脑补进第二篇,就可以完整的走一遍Yii框架的流程了。
补充:
controller文件在文章的开头提到了,有很几种render类型,比如renderPartical, renderFile, renderAjax, render
结合刚才的分析和代码的阅读,都是调用了一些函数进行了封装。
比如,renderPartical函数,就没有触发renderContent函数,代码中renderContent其实就是加载layout的内容的,那么可以推断,renderPartical就是加载的view文件,而不加载layout文件。同理,renderFile就是用户自定义文件路径,直接进行加载的。这里需要详细说明的是controller.php里面的renderAjax函数,这个函数renderAjax是以ajax方式渲染页面,可以配合js/css实现各种特效,如动态加载。
通过这三篇文章,各位可以了解到框架的运行机制,了解到框架是如何进行类的自动加载的,如何进行全局的配置的,如何构建单例模式的,如何解析路由的,如何查找文件的,如何进行加载的等等。整个过程还是很清晰的。以后的文章,将对框架的单个模块进行单独分析,不会有这种流程性的分析了。敬请期待...
张大帅
最后登录:2023-06-09
在线时长:38小时16分
- 粉丝94
- 金钱3865
- 威望50
- 积分4745
热门源码
- 基于 Yii 2 + Bootstrap 3 搭建一套后台管理系统 CMF
- 整合完 yii2-rbac+yii2-admin+adminlte 等库的基础开发后台源码
- 适合初学者学习的一款通用的管理后台
- yii-goaop - 将 goaop 集成到 Yii,在 Yii 中优雅的面向切面编程
- yii-log-target - 监控系统异常且多渠道发送异常信息通知
- 店滴云1.3.0
- 面向对象的一小步:添加 ActiveRecord 的 Scope 功能
- Yii2 开源商城 FecShop
- 基于 Yii2 开发的多店铺商城系统,免费开源 + 适合二开
- leadshop - 基于 Yii2 开发的一款免费开源且支持商业使用的商城管理系统
共 7 条评论
写的不错,格式我帮你调整了一下
十分感谢舰长
@张大帅 同一行的代码不要使用三个“
`
”,用一个 “`
”包起来才对。好吧,下次就知道了
十分感谢舰长
大帅更新速度真快
给个赞,,好好坚持
支持下!!!
赞,赞,赞!!!!!!!!!!!!!
晕头