CodeIgniter源码阅读笔记(9)——输出组件Output.php
2018-03-27 17:46
411 查看
Output组件负责向浏览器发送最终输出,是ci框架中使用非常广泛的一个组件,在之前的几篇源码阅读中,也使用到了这个组件,例如:load->view()
在阅读输出组件源码的时候,我们主要关注以下几个问题:
Output是如何输出页面的,在最终输出的之前做了哪些处理?
页面缓存是怎么实现的?
带着以上几个问题我们开始来阅读它的源码吧。
这个构造函数中一共做了三件事:
判断是否可以压缩输出内容,这是为了在之后的输出缓存中决定是否压缩;
mbstring是否可以重载,因为ci框架中对mbstring标准库的一些函数进行了重载,而输出组件中正好要使用重载的函数,详细了解可以查看’./system/core/compat/mbstring.php’文件;
ci框架中定义了一个mimes列表,将mimes列表取出,为了之后设置请求头时使用;
从构造函数中我们可以看出,output在输出内容之前,会对要输出的内容进行压缩。
这里主要注意的是压缩问题。
$_SERVER[‘HTTP_ACCEPT_ENCODING’]是判断客户端是否支持压缩
如果支持压缩且需要执行php压缩代码,如果是缓存,发送相应请求头,不是缓存,执行php的压缩代码
如果不支持压缩,如果是缓存,则在执行php的解压缩代码再输出(因为缓存的内容会进行压缩),不是缓存,不执行压缩代码
缓存的key是根据uri信息生成的md5字符串
$this->_compress_output为true对输出内容执行php压缩代码
缓存有效时间以秒为单位,缓存有效时间和请求头信息,以序列化后的数组存入缓存中,并以’ENDCI—>’字符串为分割符合输出内容分开
最后将缓存内容写入缓存文件
*
设置缓存请求头
输出缓存
在阅读CodeIngiter.php的时候,我们知道,如果对应请求有缓存的话,则不会初始化控制器,所以_display就是通过是否加载了控制器来判断输出的内容是不是缓存的
在阅读输出组件源码的时候,我们主要关注以下几个问题:
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源码阅读笔记(8)——加载器Loader.php
- CodeIgniter源码阅读笔记(1)——框架入口index.php
- CI框架源代码阅读笔记7 配置管理组件 Config.php
- CodeIgniter框架源码笔记(4)——负责屏幕上呈现的内容:输出类Output.php
- CI框架源码阅读笔记7 配置管理组件 Config.php
- CI框架源码阅读笔记7 配置管理组件 Config.php
- CI框架源码阅读笔记7 配置管理组件 Config.php
- ****CI框架源码阅读笔记7 配置管理组件 Config.php
- CodeIgniter 核心代码阅读-输出文件Output.php
- php学习笔记-3.thinkphp表的join和模板引擎输出
- CI框架源码阅读---------Output.php
- PHP输出缓存(output_buffering)小记 -- 尚有疑问
- LotusPhp笔记之:基于ObjectUtil组件的使用分析
- NonStringOrTemplateOutputException ----组件输出异常
- CI框架源码完全分析之核心文件(输出类)Output.php
- CI框架源码阅读笔记3 全局函数Common.php
- LotusPhp笔记之:Cookie组件的使用详解
- LotusPhp笔记之:Cookie组件的使用详解
- Python 教程阅读笔记(六):输入和输出
- PHP扩展开发与内核应用阅读笔记---php的作用域以及如何在扩展中定义,查找php变量