您的位置:首页 > 编程语言 > PHP开发

CodeIgniter源码阅读笔记(9)——输出组件Output.php

2018-03-27 17:46 411 查看
Output组件负责向浏览器发送最终输出,是ci框架中使用非常广泛的一个组件,在之前的几篇源码阅读中,也使用到了这个组件,例如:load->view()

在阅读输出组件源码的时候,我们主要关注以下几个问题:

Output是如何输出页面的,在最终输出的之前做了哪些处理?

页面缓存是怎么实现的?

带着以上几个问题我们开始来阅读它的源码吧。

1.构造函数

public function __construct()
{
//判断是否可压缩输出内容,zlib是压缩内容的扩展组件
$this->_zlib_oc = (bool) ini_get('zlib.output_compression');
$this->_compress_output = (
$this->_zlib_oc === FALSE
&& config_item('compress_output') === TRUE
&& extension_loaded('zlib')
);
//mbstring是php的标准扩展库,查看标准扩展库的函数是否可以重载
isset(self::$func_overload) OR self::$func_overload =
(extension_loaded('mbstring') &&
ini_get('mbstring.func_overload'));
//获取mimes配置文件中的mimes列表
$this->mimes =& get_mimes();

log_message('info', 'Output Class Initialized');
}


这个构造函数中一共做了三件事:

判断是否可以压缩输出内容,这是为了在之后的输出缓存中决定是否压缩;

mbstring是否可以重载,因为ci框架中对mbstring标准库的一些函数进行了重载,而输出组件中正好要使用重载的函数,详细了解可以查看’./system/core/compat/mbstring.php’文件;

ci框架中定义了一个mimes列表,将mimes列表取出,为了之后设置请求头时使用;

从构造函数中我们可以看出,output在输出内容之前,会对要输出的内容进行压缩。

2.输出函数——_display()

public function _display($output = '')
{
//加载Benchmark组件和Config组件
$BM =& load_class('Benchmark', 'core');
$CFG =& load_class('Config', 'core');
//获取控制器,如果是缓存则不会实例化控制器
if (class_exists('CI_Controller', FALSE))
{
$CI =& get_instance();
}
//如果output为空,则将final_output赋值给output
if ($output === '')
{
$output =& $this->final_output;
}
//当控制器没有自己的输出方法,且没有处理过缓存文件时,写缓存
if ($this->cache_expiration > 0 && isset($CI)
&& ! method_exists($CI, '_output'))
{
$this->_write_cache($output);
}
//mark开始时间
$elapsed = $BM->elapsed_time('total_execution_time_start',
'total_execution_time_end');
//$this->parse_exec_vars为true,将输出内容的
//'0.0343'和'1.07MB'替换成实际的时间和内存使用情况
if ($this->parse_exec_vars === TRUE)
{
$memory    = round(memory_get_usage() / 1024 / 1024, 2).'MB';
$output = str_replace(array('0.0343', '1.07MB'), array($elapsed, $memory), $output);
}
//控制器已加载,会压缩输出内容且请求头中Accept-Encoding中有gzip
//将输出内容压缩再放入缓冲区
if (isset($CI)
&& $this->_compress_output === TRUE
&& isset($_SERVER['HTTP_ACCEPT_ENCODING'])
&& strpos($_SERVER['HTTP_ACCEPT_ENCODING'], 'gzip') !== FALSE)
{
ob_start('ob_gzhandler');
}
//发送http请求头
if (count($this->headers) > 0)
{
foreach ($this->headers as $header)
{
@header($header[0], $header[1]);
}
}
//如果没有控制器初始化,则说明当前是一个缓冲的输出,发送数据并退出
if ( ! isset($CI))
{
//如果会压缩输出内容
if ($this->_compress_output === TRUE)
{
//请求头中Accept-Encoding中有gzip,发送http请求头
if (isset($_SERVER['HTTP_ACCEPT_ENCODING']) && strpos($_SERVER['HTTP_ACCEPT_ENCODING'], 'gzip') !== FALSE)
{
header('Content-Encoding: gzip');
header('Content-Length: '.self::strlen($output));
}
else
{
//解压压缩数据
$output = gzinflate(self::substr($output, 10, -8));
}
}
//输出内容
echo $output;
log_message('info', 'Final output sent to browser');
log_message('debug', 'Total execution time: '.$elapsed);
return;
}
//如果开启了数据分析,会生成一些性能报告,帮助我们调试
if ($this->enable_profiler === TRUE)
{
$CI->load->library('profiler');
if ( ! empty($this->_profiler_sections))
{
$CI->profiler->set_sections($this->_profiler_sections);
}

$output = preg_replace('|</body>.*?</html>|is', '', $output, -1, $count).$CI->profiler->run();
if ($count > 0)
{
$output .= '</body></html>';
}
}
//控制器中有输出函数,则调用控制器中的函数,否则直接输出
if (method_exists($CI, '_output'))
{
$CI->_output($output);
}
else
{
echo $output;
}

log_message('info', 'Final output sent to browser');
log_message('debug', 'Total execution time: '.$elapsed);
}


这里主要注意的是压缩问题。

$_SERVER[‘HTTP_ACCEPT_ENCODING’]是判断客户端是否支持压缩

如果支持压缩且需要执行php压缩代码,如果是缓存,发送相应请求头,不是缓存,执行php的压缩代码

如果不支持压缩,如果是缓存,则在执行php的解压缩代码再输出(因为缓存的内容会进行压缩),不是缓存,不执行压缩代码

3.写缓存_write_cache

缓存的目录可以是自定义目录,默认是’application/cache’目录

$path = $CI->config->item('cache_path');
$cache_path = ($path === '') ? APPPATH.'cache/' : $path;


缓存的key是根据uri信息生成的md5字符串

$uri = $CI->config->item('base_url')
.$CI->config->item('index_page')
.$CI->uri->uri_string();
if (($cache_query_string = $CI->config->item('cache_query_string'))
&& ! empty($_SERVER['QUERY_STRING']))
{
if (is_array($cache_query_string))
{
$uri .= '?'.http_build_query(array_intersect_key
($_GET, array_flip($cache_query_string)));
}
else
{
$uri .= '?'.$_SERVER['QUERY_STRING'];
}
}
$cache_path .= md5($uri);


$this->_compress_output为true对输出内容执行php压缩代码

if ($this->_compress_output === TRUE)
{
$output = gzencode($output);

if ($this->get_header('content-type') === NULL)
{
$this->set_content_type($this->mime_type);
}
}


缓存有效时间以秒为单位,缓存有效时间和请求头信息,以序列化后的数组存入缓存中,并以’ENDCI—>’字符串为分割符合输出内容分开

$expire = time() + ($this->cache_expiration * 60);

$cache_info = serialize(array(
'expire'    => $expire,
'headers'   => $this->headers
));

$output = $cache_info.'ENDCI--->'.$output;


最后将缓存内容写入缓存文件

4.输出缓存内容——_display_cache函数

_display_cache
会通过Key找到对应的缓存文件,然后取出缓存文件中的内容,查看缓存信息,反序列化缓存信息,得到缓存有效时间,判断缓存是否有效,无效则删除缓存,返回FALSE

*
$_SERVER['REQUEST_TIME']
参数是请求时间

if ( ! preg_match('/^(.*)ENDCI--->/', $cache, $match))
{
return FALSE;
}

$cache_info = unserialize($match[1]);
$expire = $cache_info['expire'];

$last_modified = filemtime($filepath);

if ($_SERVER['REQUEST_TIME'] >= $expire && is_really_writable($cache_path))
{
@unlink($filepath);
log_message('debug', 'Cache file has expired. File deleted.');
return FALSE;
}


设置缓存请求头

$this->set_cache_header($last_modified, $expire);

foreach ($cache_info['headers'] as $header)
{
$this->set_header($header[0], $header[1]);
}


输出缓存

在阅读CodeIngiter.php的时候,我们知道,如果对应请求有缓存的话,则不会初始化控制器,所以_display就是通过是否加载了控制器来判断输出的内容是不是缓存的

$this->_display(self::substr($cache, self::strlen($match[0])));


5.设置缓存请求头——set_cache_header函数

public function set_cache_header($last_modified, $expiration)
{
$max_age = $expiration - $_SERVER['REQUEST_TIME'];

if (isset($_SERVER['HTTP_IF_MODIFIED_SINCE']) &&
$last_modified <= strtotime($_SERVER['HTTP_IF_MODIFIED_SINCE']))
{
$this->set_status_header(304);
exit;
}

header('Pragma: public');
header('Cache-Control: max-age='.$max_age.', public');
header('Expires: '.gmdate('D, d M Y H:i:s', $expiration).' GMT');
header('Last-modified: '.gmdate('D, d M Y H:i:s', $last_modified).' GMT');
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  codeigniter php