前言
CI 的 View 没有像 Laravel 等一些流行框架一样设计的那么重,有自己的一套模版机制,CI 一直采用纯天然的 PHP 模板形式,纯天然的好处是不用再学习一套模板语言了,缺点是不能用到一些便利的设计模式,比如不能使用继承布局等等,当然你也可以加第三方的视图组件进来。
下面我们开始看源码,看源码,我们先从视图的调用开始。
视图的调用
CI 4 开始使用新的视图调用逻辑,不在是 load 形式,调用方式如下:
echo view('name');
可以看到是直接调用了一个 view 函数,这个函数我们既不需要提前 load , 在 construct 方法里也没看见 include 什么文件,同时,这个方法看起来又不属于控制器对象,那么它是从哪里来的呢?
回到之前写的“之二——入口以及初始化操作”一节,里面提到了,在 bootstrap.php 74 行(原始文件行号), require 了一下 BASEPATH.'Common.php' ,这个文件中定义了许多辅助方法。 view 就是其中一个,该方法位于 system/common.php 中的 88 行(原始文件行号)。下面把代码贴出来:
if (! function_exists('view'))
{
function view(string $name, array $data = [], array $options = [])
{
$renderer = Services::renderer();
$saveData = null;
if (array_key_exists('saveData', $options) && $options['saveData'] === true)
{
$saveData = (bool)$options['saveData'];
unset($options['saveData']);
}
return $renderer->setData($data, 'raw')
->render($name, $options, $saveData);
}
}
可以看到,这里调用了 Services 类的 renderer 静态方法。之后的 saveData 逻辑主要处理多次调用 view 方法时是否共享视图变量以及最后把要传递给视图的数据变量通过 $renderer->setData 方法传递给 render ,最后又执行了 render 进行渲染视图。下面贴出的是 Services::renderer() 源码(system/config/services.php:362):
public static function renderer($viewPath = APPPATH.'Views/', $config = null, $getShared = true)
{
if ($getShared)
{
return self::getSharedInstance('renderer', $viewPath, $config);
}
if (is_null($config))
{
$config = new \Config\View();
}
return new \CodeIgniter\View\View($config, $viewPath, self::locator(true), CI_DEBUG, self::logger(true));
}
可以看出, view 方法主要 new 了一个 CodeIgniterViewView 类,该类位于 /system/ViewView.php 下。
小结一下,给个分析过程图,以方便理解:
接下来就是我们的主角 View 了。
View 源码分析
按着以上图中流程,我们要看 View 类的三个关键方法,分别是 __construct 、 setData 、 render 。
__construct 方法
public function __construct($config, string $viewPath = null, $loader = null, bool $debug = null, Logger $logger = null)
{
$this->config = $config;
$this->viewPath = rtrim($viewPath, '/ ').'/';
$this->loader = is_null($loader) ? Services::locator() : $loader;
$this->logger = is_null($logger) ? Services::logger() : $logger;
$this->debug = is_null($debug) ? CI_DEBUG : $debug;
$this->saveData = $config->saveData ?? null;
}
可以看到在 services new 的时候,仅仅传递了配置信息以及视图路径,视图数据不在初始化之列。
setData 方法
public function setData(array $data=[], string $context=null): RendererInterface
{
if ( ! empty($context))
{
$data = \esc($data, $context);
}
$this->data = array_merge($this->data, $data);
return $this;
}
此方法主要用途是往视图里压数据,实际上就是把新压的数据和对象中原有的数据(数据)合并一下。
render 方法
作为视图逻辑,渲染视图肯定是一个重中之重的过程。
以下是去掉注释和空行的源码截图(源码分析中涉及到的行号是截图中的行号):
142 行:由参数可以看出,调用 render 方法时才把具体的视图文件名传递进来,因视图数据通过 setData 方法放到了当前对象的 data 属性里,因此无需再次传递。
145-147,170-172 行,处理是否将本次压进来的视图数据共享给下次 render 过程。这个 $saveData 可以在 application/config/view.php 里配置,默认是 false 。
149 行:处理视图文件名后缀。
150-158 行,判断开始缓存设置的话,处理视图缓存。
159-168 行,尝试着通过自动加载机制找到视图文件。找不到,抛异常。
168 行,很重要(划重点),该方法是将压进来的数组形式的数据扩展开成 $key=$value 形式,因为视图是 include 进来的普通 php 因此,在视图中也就可以用 $key 的形式读取到变量的内容。
174-177 行,开启输出控制缓冲机制,并 include 进来视图,相当于同时执行了这个文件,这个文件中的普通 html 亦或是执行 php 后的输出,都会被输出缓冲接收到并赋值给了 $output 。
179-182 行,前边的缓存是处理读取过程,这里是处理写入过程。
183 行,最后返回渲染结果。
结语
从源码上看, CI 使用了原始 PHP 作为模版机制使得视图逻辑非常简单。无非也就是把视图 include 进来,用输出缓冲把执行结果拿到即可。
此文可以转载,但转载前需要发邮件到imustgxd*sina.cn进行沟通,未沟通的均视作侵权。 转载同时需注明链接,并保留此段文字。