前言

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 方法

作为视图逻辑,渲染视图肯定是一个重中之重的过程。

以下是去掉注释和空行的源码截图(源码分析中涉及到的行号是截图中的行号):

view类的render方法

  1. 142 行:由参数可以看出,调用 render 方法时才把具体的视图文件名传递进来,因视图数据通过 setData 方法放到了当前对象的 data 属性里,因此无需再次传递。

  2. 145-147,170-172 行,处理是否将本次压进来的视图数据共享给下次 render 过程。这个 $saveData 可以在 application/config/view.php 里配置,默认是 false 。

  3. 149 行:处理视图文件名后缀。

  4. 150-158 行,判断开始缓存设置的话,处理视图缓存。

  5. 159-168 行,尝试着通过自动加载机制找到视图文件。找不到,抛异常。

  6. 168 行,很重要(划重点),该方法是将压进来的数组形式的数据扩展开成 $key=$value 形式,因为视图是 include 进来的普通 php 因此,在视图中也就可以用 $key 的形式读取到变量的内容。

  7. 174-177 行,开启输出控制缓冲机制,并 include 进来视图,相当于同时执行了这个文件,这个文件中的普通 html 亦或是执行 php 后的输出,都会被输出缓冲接收到并赋值给了 $output 。

  8. 179-182 行,前边的缓存是处理读取过程,这里是处理写入过程。

  9. 183 行,最后返回渲染结果。

结语

从源码上看, CI 使用了原始 PHP 作为模版机制使得视图逻辑非常简单。无非也就是把视图 include 进来,用输出缓冲把执行结果拿到即可。

此文可以转载,但转载前需要发邮件到imustgxd*sina.cn进行沟通,未沟通的均视作侵权。 转载同时需注明链接,并保留此段文字。


sheld
652 声望33 粉丝

我是什么都不知道啊