输出类负责将用户请求转化为浏览器输出的,理解输出类需要对HTTP协议有一定的理解,知道服务器如何将请求转化的文件送到浏览器,知道Http的header和content中与服务器输出有关的标签的含义,才能更好的理解CI中输出类CI_Output需要完成的工作。
这里就缓存的例子进行说明,服务器缓存在CI中是如何实现的呢?首先缓存的形式是什么?简单点的实现用文件缓存,那么就需要考虑写缓存的互斥问题。如何判断缓存是否命中?用基础的方法给缓存标记一个key——uri的md5,比较这个key是否相等即可。那么这里又要考虑是否对原始的uri进行md5操作,需要对uri进行标准化处理吗?需要考虑查询字符串吗?后续的内容还包括如何判断缓存过期,缓存输出的时候如何处理HTTP响应头中与缓存控制相关的标签,如果缓存未命中应该如何通知其他组件进行后续流程等等。
不过值得庆幸的是以上内容在CI框架中都已经实现了,下面我们开始分析CI_Output类是如何做到这些的。
属性概览
属性名称 | 注释 |
---|---|
public $final_output; | 保存最终的输出结果 |
public $cache_expiration = 0; | 缓存生效时间,默认为0 |
public $headers = array(); | headers列表 |
public $mimes = array(); | mimies类型列表 |
protected $mime_type = 'text/html'; | text/html类型 |
public $enable_profiler = FALSE; | 保存用户profile标志位 |
protected $_zlib_oc = FALSE; | ini开启zlib标志位 |
protected $_compress_output = FALSE; | 是否压缩输出标志位 |
protected $_profiler_sections = array(); | 保存用户profile的数组 |
protected static $func_overload; | 函数重载标志位 |
public $parse_exec_vars = TRUE; |
方法概览
方法名称 | 注释 |
---|---|
__construct() | 构造函数 |
get_output() | 获取并返回$final_output |
set_output($output) | 设置$final_output返回类 |
append_output($output) | 以append的方式修改$final_output,返回类 |
set_header($header, $replace = TRUE) | 设置header |
set_content_type($mime_type, $charset = NULL) | 设置内容的类型 |
get_content_type() | 获取内容的类型 |
get_header($header) | 获取header |
set_status_header($code = 200, $text = '') | 设置header的状态 |
enable_profiler($val = TRUE) | 是否保存profile |
set_profiler_sections($sections) | 保存profile |
cache($time) | 设置缓存有效时间 |
_display($output = '') | 将输出展示到浏览器 |
_write_cache($output) | 写文件缓存的放发 |
_display_cache(&$CFG, &$URI) | 读取缓存输出到浏览器 |
delete_cache($uri = '') | 删除缓存 |
set_cache_header($last_modified, $expiration) | 设置缓存相关的header |
构造函数__construct()
public function __construct()
{
//获取php的ini对压缩输出的配置
$this->_zlib_oc = (bool) ini_get('zlib.output_compression');
//当php环境没有开启gzip压缩,且配置文件设置compress_output为Ture,安装了zlib扩展
//将属性_compress_output设置为True,通常情况下是启用服务器压缩而关闭程序本身的压缩功能
$this->_compress_output = (
$this->_zlib_oc === FALSE
&& config_item('compress_output') === TRUE
&& extension_loaded('zlib')
);
isset(self::$func_overload) OR self::$func_overload = (extension_loaded('mbstring') && ini_get('mbstring.func_overload'));
//获取mimes的类型
$this->mimes =& get_mimes();
log_message('info', 'Output Class Initialized');
}
把输出显示在浏览器_display($output = '')
public function _display($output = '')
{
// Note:我们实用load_class()而不直接用$CI =& get_instance()
// 因为有时候本方法是被缓存机制调用的,这时候$CI超级对象还无法使用
$BM =& load_class('Benchmark', 'core');
$CFG =& load_class('Config', 'core');
//如果可能的话,获取超级对象$CI
if (class_exists('CI_Controller', FALSE))
{
$CI =& get_instance();
}
//为属性$output赋值
if ($output === '')
{
$output =& $this->final_output;
}
//当$CI对象存在时证明我们不是在从缓存输出数据,这时如果Controller没有自定义_output方法就需要写缓存
if ($this->cache_expiration > 0 && isset($CI) && ! method_exists($CI, '_output'))
{
$this->_write_cache($output);
}
//解析出请求耗时和内存实用并替换输出中的伪变量数组
$elapsed = $BM->elapsed_time('total_execution_time_start', 'total_execution_time_end');
if ($this->parse_exec_vars === TRUE)
{
$memory = round(memory_get_usage() / 1024 / 1024, 2).'MB';
$output = str_replace(array('{elapsed_time}', '{memory_usage}'), array($elapsed, $memory), $output);
}
//根据需要压缩文件
if (isset($CI) //通过$CI判断我们不是在读缓存文件,如果是文件可能已经压缩过了
&& $this->_compress_output === TRUE
&& isset($_SERVER['HTTP_ACCEPT_ENCODING']) && strpos($_SERVER['HTTP_ACCEPT_ENCODING'], 'gzip') !== FALSE)
{
ob_start('ob_gzhandler');
}
//如果headers不为空,则设置为返回到browser的header
if (count($this->headers) > 0)
{
foreach ($this->headers as $header)
{
@header($header[0], $header[1]);
}
}
//如果$CI变量不存在,我们是在从缓存读取数据,直接读取并退出即可
if ( ! isset($CI))
{
if ($this->_compress_output === TRUE)
{
if (isset($_SERVER['HTTP_ACCEPT_ENCODING']) && strpos($_SERVER['HTTP_ACCEPT_ENCODING'], 'gzip') !== FALSE)
{
header('Content-Encoding: gzip');
header('Content-Length: '.self::strlen($output));
}
else
{
//User agent不支持gzip格式的数据,我们需要将缓存解压缩
$output = gzinflate(self::substr($output, 10, -8));
}
}
//直接将$output输出
echo $output;
log_message('info', 'Final output sent to browser');
log_message('debug', 'Total execution time: '.$elapsed);
return;
}
//如果需要收集用户profile则加载类库profile处理
if ($this->enable_profiler === TRUE)
{
$CI->load->library('profiler');
if ( ! empty($this->_profiler_sections))
{
$CI->profiler->set_sections($this->_profiler_sections);
}
//如果输出数据中包含关闭的