上一节我们最后说到了CodeIgniter.php,可是这一节的标题是Common.php,有的朋友可能会觉得很奇怪。事实上,CodeIgniter.php其实包含了ci框架启动的整个流程。

里面引入了各种类文件,然后调用其中的方法,完成所有操作。我们先分析一下引入的各个文件,最后再看CodeIgniter.php,个人觉得这样学习起来更加清晰明了。

查看代码可以发现,其实引入的第一个文件是constants.php,文件里面都是定义一下常量,没什么可以说的,所以我们往下看,开始分析Common.php.

Common.php文件中主要是全局使用的一些方法。

defined('BASEPATH') OR exit('No direct script access allowed');

第一行判断有没有定义BASEPATH常量,如果没有则退出

if ( ! function_exists('is_php'))
{
/**
* Determines if the current version of PHP is equal to or greater than the supplied value
*
* @param string
* @return bool TRUE if the current version is $version or higher
*/
function is_php($version)
{
static $_is_php;
$version = (string) $version; if ( ! isset($_is_php[$version]))
{
$_is_php[$version] = version_compare(PHP_VERSION, $version, '>=');
} return $_is_php[$version];
}
}

函数定义前都先判断了是否存在,只有函数不存在时才开始定义。

is_php是用来判断当前使用的php版本是否等于给定的版本或是更高。

注意这里使用了static来声明了一个静态变量$_is_php,它会在第一次使用函数时初始化,然后对其赋值,方法调用结束后,该变量仍然存在,再次调用函数直接使用即可,静态变量只会执行一次初始化,具体内容参考static相关使用方法。后面的许多函数都有这样的代码,不再一一赘述。

if ( ! function_exists('is_really_writable'))
{
/**
* Tests for file writability
*
* is_writable() returns TRUE on Windows servers when you really can't write to
* the file, based on the read-only attribute. is_writable() is also unreliable
* on Unix servers if safe_mode is on.
*
* @link https://bugs.php.net/bug.php?id=54709
* @param string
* @return bool
*/
function is_really_writable($file)
{
// If we're on a Unix server with safe_mode off we call is_writable
if (DIRECTORY_SEPARATOR === '/' && (is_php('5.4') OR ! ini_get('safe_mode')))
{
return is_writable($file);
} /* For Windows servers and safe_mode "on" installations we'll actually
* write a file then read it. Bah...
*/
if (is_dir($file))
{
$file = rtrim($file, '/').'/'.md5(mt_rand());
if (($fp = @fopen($file, 'ab')) === FALSE)
{
return FALSE;
} fclose($fp);
@chmod($file, 0777);
@unlink($file);
return TRUE;
}
elseif ( ! is_file($file) OR ($fp = @fopen($file, 'ab')) === FALSE)
{
return FALSE;
} fclose($fp);
return TRUE;
}
}
is_really_writable函数用来判断文件是否可写。
在php中有一个内置函数is_writable,但是它在windows系统中无法准确判断文件是否可些,如果文件只是可读,它也会返回true;同样的在unix系统中,当safe_mode被设置为on时也不会奏效,所以ci框架在这里重新封装了方法。
DIRECTORY_SEPARATOR是php的内部常量,用来获取系统的分隔符,unix下是/,而在windows下是/或者\(然而我在windows下打印了下,显示的是\)
这样再看这个函数就很容易理解了,首先我们判断是unix系统,并且safe_mode没有开启,那么可以直接使用is_writable判断是否可些。否则,如果是目录的话,我们就在该目录下创建一个新文件,成功证明可写;如果是文件,写入方式打开,成功证明可写。
if ( ! function_exists('load_class'))
{
/**
* Class registry
*
* This function acts as a singleton. If the requested class does not
* exist it is instantiated and set to a static variable. If it has
* previously been instantiated the variable is returned.
*
* @param string the class name being requested
* @param string the directory where the class should be found
* @param string an optional argument to pass to the class constructor
* @return object
*/
function &load_class($class, $directory = 'libraries', $param = NULL)
{
static $_classes = array(); // Does the class exist? If so, we're done...
if (isset($_classes[$class]))
{
return $_classes[$class];
} $name = FALSE; // Look for the class first in the local application/libraries folder
// then in the native system/libraries folder
foreach (array(APPPATH, BASEPATH) as $path)
{
if (file_exists($path.$directory.'/'.$class.'.php'))
{
$name = 'CI_'.$class; if (class_exists($name, FALSE) === FALSE)
{
require_once($path.$directory.'/'.$class.'.php');
} break;
}
} // Is the request a class extension? If so we load it too
if (file_exists(APPPATH.$directory.'/'.config_item('subclass_prefix').$class.'.php'))
{
$name = config_item('subclass_prefix').$class; if (class_exists($name, FALSE) === FALSE)
{
require_once(APPPATH.$directory.'/'.$name.'.php');
}
} // Did we find the class?
if ($name === FALSE)
{
// Note: We use exit() rather than show_error() in order to avoid a
// self-referencing loop with the Exceptions class
set_status_header(503);
echo 'Unable to locate the specified class: '.$class.'.php';
exit(5); // EXIT_UNK_CLASS
} // Keep track of what we just loaded
is_loaded($class); $_classes[$class] = isset($param)
? new $name($param)
: new $name();
return $_classes[$class];
}
} // -------------------------------------------------------------------- if ( ! function_exists('is_loaded'))
{
/**
* Keeps track of which libraries have been loaded. This function is
* called by the load_class() function above
*
* @param string
* @return array
*/
function &is_loaded($class = '')
{
static $_is_loaded = array(); if ($class !== '')
{
$_is_loaded[strtolower($class)] = $class;
} return $_is_loaded;
}
}

这里注意函数名前有一个引用&符号,当用&load_class形式使用时,返回的是一个类实例的引用,你对该类的修改会影响下一次的调用结果。

php的引用和其他语言不是很一样,注意它不是指针,具体内容参考这别博文http://www.cnblogs.com/thinksasa/p/3334492.html,写的很好。

我们来看代码,先判断是否有缓存($_classes),如果有直接调用。没有的话,我们先引入文件,优先应用目录,然后系统目录。在判断有没有MY_前缀的文件(前缀根据配置),如有则引入它。

调用is_loaded方法,目的在一个变量中储存已经加载过的类。最后实例化,然后返回。

    function &get_config(Array $replace = array())
{
static $config; if (empty($config))
{
$file_path = APPPATH.'config/config.php';
$found = FALSE;
if (file_exists($file_path))
{
$found = TRUE;
require($file_path);
} // Is the config file in the environment folder?
if (file_exists($file_path = APPPATH.'config/'.ENVIRONMENT.'/config.php'))
{
require($file_path);
}
elseif ( ! $found)
{
set_status_header(503);
echo 'The configuration file does not exist.';
exit(3); // EXIT_CONFIG
} // Does the $config array exist in the file?
if ( ! isset($config) OR ! is_array($config))
{
set_status_header(503);
echo 'Your config file does not appear to be formatted correctly.';
exit(3); // EXIT_CONFIG
}
} // Are any values being dynamically added or replaced?
foreach ($replace as $key => $val)
{
$config[$key] = $val;
} return $config;
}

目的是获取config.php文件,提供这个方法让我们在没有引入Config类的时候也可以获取配置。

先判断是否有缓存,如果没有引入文件,注意这一行if ( ! isset($config) OR ! is_array($config))是为了判断引入是否成功,至于$config变量是在引入的配置文件中设置的,不细说了。

该方法可以传入一个数组作为参数,用来替换配置文件中预先定义好的配置。

    function config_item($item)
{
static $_config; if (empty($_config))
{
// references cannot be directly assigned to static variables, so we use an array
$_config[0] =& get_config();
} return isset($_config[0][$item]) ? $_config[0][$item] : NULL;
}

config_item紧接着上面的get_config方法,用来在之前获得的配置信息中获取相应key的值。

    function &get_mimes()
{
static $_mimes; if (empty($_mimes))
{
if (file_exists(APPPATH.'config/'.ENVIRONMENT.'/mimes.php'))
{
$_mimes = include(APPPATH.'config/'.ENVIRONMENT.'/mimes.php');
}
elseif (file_exists(APPPATH.'config/mimes.php'))
{
$_mimes = include(APPPATH.'config/mimes.php');
}
else
{
$_mimes = array();
}
} return $_mimes;
}

引入mimes.php这个配置文件,里面配置了一些mimes_type

    function is_https()
{
if ( ! empty($_SERVER['HTTPS']) && strtolower($_SERVER['HTTPS']) !== 'off')
{
return TRUE;
}
elseif (isset($_SERVER['HTTP_X_FORWARDED_PROTO']) && strtolower($_SERVER['HTTP_X_FORWARDED_PROTO']) === 'https')
{
return TRUE;
}
elseif ( ! empty($_SERVER['HTTP_FRONT_END_HTTPS']) && strtolower($_SERVER['HTTP_FRONT_END_HTTPS']) !== 'off')
{
return TRUE;
} return FALSE;
}

用来判断是否是使用https

    function is_cli()
{
return (PHP_SAPI === 'cli' OR defined('STDIN'));
}

判断是否是cli方式(也就是命令行调用),常量PHP_SAPI在不同情况下值不一样,我在ubuntu+nginx环境下打印为fpm-fcgi,命令行下为cli,感兴趣的朋友可以自行打印

function show_error($message, $status_code = 500, $heading = 'An Error Was Encountered')
{
$status_code = abs($status_code);
if ($status_code < 100)
{
$exit_status = $status_code + 9; // 9 is EXIT__AUTO_MIN
if ($exit_status > 125) // 125 is EXIT__AUTO_MAX
{
$exit_status = 1; // EXIT_ERROR
} $status_code = 500;
}
else
{
$exit_status = 1; // EXIT_ERROR
} $_error =& load_class('Exceptions', 'core');
echo $_error->show_error($heading, $message, 'error_general', $status_code);
exit($exit_status);
}

这是一个用来显示错误信息的方法,核心内容就是引入Exception类,然后调用里面的方法,show_error方法的具体实现我们后面单独解说这个类的时候再细看。

function show_404($page = '', $log_error = TRUE)
{
$_error =& load_class('Exceptions', 'core');
$_error->show_404($page, $log_error);
exit(4); // EXIT_UNKNOWN_FILE
}

跟上面一样,没啥可说的。

function log_message($level, $message)
{
static $_log; if ($_log === NULL)
{
// references cannot be directly assigned to static variables, so we use an array
$_log[0] =& load_class('Log', 'core');
} $_log[0]->write_log($level, $message);
}

引入Log类,用来记录日志信息。

function set_status_header($code = 200, $text = '')
{
if (is_cli())
{
return;
} if (empty($code) OR ! is_numeric($code))
{
show_error('Status codes must be numeric', 500);
} if (empty($text))
{
is_int($code) OR $code = (int) $code;
$stati = array(
100 => 'Continue',
101 => 'Switching Protocols', 200 => 'OK',
201 => 'Created',
202 => 'Accepted',
203 => 'Non-Authoritative Information',
204 => 'No Content',
205 => 'Reset Content',
206 => 'Partial Content', 300 => 'Multiple Choices',
301 => 'Moved Permanently',
302 => 'Found',
303 => 'See Other',
304 => 'Not Modified',
305 => 'Use Proxy',
307 => 'Temporary Redirect', 400 => 'Bad Request',
401 => 'Unauthorized',
402 => 'Payment Required',
403 => 'Forbidden',
404 => 'Not Found',
405 => 'Method Not Allowed',
406 => 'Not Acceptable',
407 => 'Proxy Authentication Required',
408 => 'Request Timeout',
409 => 'Conflict',
410 => 'Gone',
411 => 'Length Required',
412 => 'Precondition Failed',
413 => 'Request Entity Too Large',
414 => 'Request-URI Too Long',
415 => 'Unsupported Media Type',
416 => 'Requested Range Not Satisfiable',
417 => 'Expectation Failed',
422 => 'Unprocessable Entity',
426 => 'Upgrade Required',
428 => 'Precondition Required',
429 => 'Too Many Requests',
431 => 'Request Header Fields Too Large', 500 => 'Internal Server Error',
501 => 'Not Implemented',
502 => 'Bad Gateway',
503 => 'Service Unavailable',
504 => 'Gateway Timeout',
505 => 'HTTP Version Not Supported',
511 => 'Network Authentication Required',
); if (isset($stati[$code]))
{
$text = $stati[$code];
}
else
{
show_error('No status text available. Please check your status code number or supply your own message text.', 500);
}
} if (strpos(PHP_SAPI, 'cgi') === 0)
{
header('Status: '.$code.' '.$text, TRUE);
}
else
{
$server_protocol = isset($_SERVER['SERVER_PROTOCOL']) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.1';
header($server_protocol.' '.$code.' '.$text, TRUE, $code);
}
}

用来设置header头部的返回,可以自己调用这个方法测试一下,会发现在浏览器中返回的header内容不同。

function _error_handler($severity, $message, $filepath, $line)
{
$is_error = (((E_ERROR | E_PARSE | E_COMPILE_ERROR | E_CORE_ERROR | E_USER_ERROR) & $severity) === $severity); // When an error occurred, set the status header to '500 Internal Server Error'
// to indicate to the client something went wrong.
// This can't be done within the $_error->show_php_error method because
// it is only called when the display_errors flag is set (which isn't usually
// the case in a production environment) or when errors are ignored because
// they are above the error_reporting threshold.
if ($is_error)
{
set_status_header(500);
} // Should we ignore the error? We'll get the current error_reporting
// level and add its bits with the severity bits to find out.
if (($severity & error_reporting()) !== $severity)
{
return;
} $_error =& load_class('Exceptions', 'core');
$_error->log_exception($severity, $message, $filepath, $line); // Should we display the error?
if (str_ireplace(array('off', 'none', 'no', 'false', 'null'), '', ini_get('display_errors')))
{
$_error->show_php_error($severity, $message, $filepath, $line);
} // If the error is fatal, the execution of the script should be stopped because
// errors can't be recovered from. Halting the script conforms with PHP's
// default error handling. See http://www.php.net/manual/en/errorfunc.constants.php
if ($is_error)
{
exit(1); // EXIT_ERROR
}
}

_error_handler是为了作为自定义的错误处理程序来处理错误,在CodeInniter.php可以看到有这样一句set_error_handler('_error_handler');就是设置自定义处理函数。

现在我们来看代码,第一行是这个$is_error = (((E_ERROR | E_PARSE | E_COMPILE_ERROR | E_CORE_ERROR | E_USER_ERROR) & $severity) === $severity);注意这里的&和|是位运算。当我们出现的错误是上面几种错误的时候,进行与运算后错误码并没有发生变化,判断后得到$is_error为true,这个时候set_status_header(500),否则不需要设置500。

if (($severity & error_reporting()) !== $severity)获取应该报告何种错误,如果不需要报错,退出当前方法。

$_error->log_exception($severity, $message, $filepath, $line)用来记录日志。

if (str_ireplace(array('off', 'none', 'no', 'false', 'null'), '', ini_get('display_errors')))判断报错是否开启,开启的话打印错误。

最后判断$is_error为true,因为这种情况表示是严重的错误了,所以执行exit。

function _exception_handler($exception)
{
$_error =& load_class('Exceptions', 'core');
$_error->log_exception('error', 'Exception: '.$exception->getMessage(), $exception->getFile(), $exception->getLine()); is_cli() OR set_status_header(500);
// Should we display the error?
if (str_ireplace(array('off', 'none', 'no', 'false', 'null'), '', ini_get('display_errors')))
{
$_error->show_exception($exception);
} exit(1); // EXIT_ERROR
}

异常处理函数,跟上面的错误处理类似,先记录日志,然后判断是否为cli,不是的话报500,打印异常信息,exit终止执行。

function _shutdown_handler()
{
$last_error = error_get_last();
if (isset($last_error) &&
($last_error['type'] & (E_ERROR | E_PARSE | E_CORE_ERROR | E_CORE_WARNING | E_COMPILE_ERROR | E_COMPILE_WARNING)))
{
_error_handler($last_error['type'], $last_error['message'], $last_error['file'], $last_error['line']);
}
}

php终止时执行的函数。

$last_error = error_get_last();获取终止执行前最后一次错误,如果判断错误类型是这几种E_ERROR | E_PARSE | E_CORE_ERROR | E_CORE_WARNING | E_COMPILE_ERROR | E_COMPILE_WARNING,那么调用错误处理函数。

function remove_invisible_characters($str, $url_encoded = TRUE)
{
$non_displayables = array(); // every control character except newline (dec 10),
// carriage return (dec 13) and horizontal tab (dec 09)
if ($url_encoded)
{
$non_displayables[] = '/%0[0-8bcef]/i'; // url encoded 00-08, 11, 12, 14, 15
$non_displayables[] = '/%1[0-9a-f]/i'; // url encoded 16-31
} $non_displayables[] = '/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]+/S'; // 00-08, 11, 12, 14-31, 127 do
{
$str = preg_replace($non_displayables, '', $str, -1, $count);
}
while ($count); return $str;
}

用来去除字符串中看不见的字符。主要是用正则表达式,将不可见字符替换为空。

function html_escape($var, $double_encode = TRUE)
{
if (empty($var))
{
return $var;
} if (is_array($var))
{
foreach (array_keys($var) as $key)
{
$var[$key] = html_escape($var[$key], $double_encode);
} return $var;
} return htmlspecialchars($var, ENT_QUOTES, config_item('charset'), $double_encode);
}

用来将预定义的字符转换成HTML实体。

html_escape是一个递归函数,当参数为数组时调用自身,一直到字符串时执行htmlspecialchars方法。

function _stringify_attributes($attributes, $js = FALSE)
{
$atts = NULL; if (empty($attributes))
{
return $atts;
} if (is_string($attributes))
{
return ' '.$attributes;
} $attributes = (array) $attributes; foreach ($attributes as $key => $val)
{
$atts .= ($js) ? $key.'='.$val.',' : ' '.$key.'="'.$val.'"';
} return rtrim($atts, ',');
}
_stringify_attributes代码很简单,就是将字符串或数组转换成指定的形式,我暂时还不清楚这个函数会在什么情况使用。
function function_usable($function_name)
{
static $_suhosin_func_blacklist; if (function_exists($function_name))
{
if ( ! isset($_suhosin_func_blacklist))
{
$_suhosin_func_blacklist = extension_loaded('suhosin')
? explode(',', trim(ini_get('suhosin.executor.func.blacklist')))
: array();
} return ! in_array($function_name, $_suhosin_func_blacklist, TRUE);
} return FALSE;
}

用来判断函数是否可用。

先判断函数是否存在,如果不存在直接返回false。在判断是否引入了扩展suhosin,如果引入了获取他的配置blacklist,如果不再其中的那么这个函数是可用的。

全部看下来,Common.php主要是定义了一些全局函数,方便ci框架调用,该文件的内容还是比较多的,over.

CI框架源码学习笔记2——Common.php的更多相关文章

  1. CI框架源码学习笔记1——index.php

    做php开发一年多了,陆陆续续用过tp/ci/yii框架,一直停留在只会使用的层面上,关于框架内部的结构实际上是不甚了解的.为了深入的学习,决定把CI框架的源码从头到尾的学习一下, 主要因为CI框架工 ...

  2. CI框架源码学习笔记7——Utf8.php

    愉快的清明节假期结束了,继续回到CI框架学习.这一节我们来看看Utf8.php文件,它主要是用来做utf8编码,废话不多说,上代码. class CI_Utf8 { /** * Class const ...

  3. CI框架源码学习笔记5——Hooks.php

    接着Benchmark.php往下看,下一个引入的文件是Hooks.php,我们称之为钩子.它的目的是在不改变核心文件的基础上,来修改框架的内部运作流程.具体使用方法参见手册http://codeig ...

  4. CI框架源码学习笔记4——Benchmark.php

    我们回到Codeigniter.php上继续往下看,第一个引入的类文件是Benchmark.php,这个文件主要是提供基准测试,具体使用方法参考手册http://codeigniter.org.cn/ ...

  5. CI框架源码学习笔记3——Log.php

    上一节说完了Common.php,然而跟代码打交道总是免不了日志记录,所以这一节我们说说Log.php文件. 先看看类里面的几个属性, protected $_log_path;  日志路径 prot ...

  6. CI框架源码学习笔记6——Config.php

    接着上一节往下,我们这一节来看看配置类Config.php,对应手册内容http://codeigniter.org.cn/user_guide/libraries/config.html. clas ...

  7. CI框架源码阅读笔记3 全局函数Common.php

    从本篇开始,将深入CI框架的内部,一步步去探索这个框架的实现.结构和设计. Common.php文件定义了一系列的全局函数(一般来说,全局函数具有最高的加载优先权,因此大多数的框架中BootStrap ...

  8. CI框架源码阅读笔记4 引导文件CodeIgniter.php

    到了这里,终于进入CI框架的核心了.既然是“引导”文件,那么就是对用户的请求.参数等做相应的导向,让用户请求和数据流按照正确的线路各就各位.例如,用户的请求url: http://you.host.c ...

  9. CI框架源码阅读笔记2 一切的入口 index.php

    上一节(CI框架源码阅读笔记1 - 环境准备.基本术语和框架流程)中,我们提到了CI框架的基本流程,这里再次贴出流程图,以备参考: 作为CI框架的入口文件,源码阅读,自然由此开始.在源码阅读的过程中, ...

随机推荐

  1. 2014.10.1 Cmd更改系统时间

    Process p = new Process(); //Process类有一个StartInfo属性 //设定程序名 p.StartInfo.FileName = "cmd.exe&quo ...

  2. jsp与struts的区别

    JSP通常用于MVC的View层,Struts1,Struts2用于MVC的Control层. JSP用来展示页面信息,使用servlet API封装而成,代替servlet中response向客户端 ...

  3. Delphi 转圈 原型进度条 AniIndicator 及线程配合使用

    Delphi FMX 转圈 原型进度条 progress AniIndicator TAniIndicator TFloatAnimation VCL下也有转圈菊花进度条 TActivityIndic ...

  4. 【Android 多媒体应用】使用MediaRecoder录制,MediaPlayer播放音频数据

    1.MainActivity.java import android.annotation.TargetApi; import android.app.Activity; import android ...

  5. vs中ffmpeg release版本崩溃问题(转)

    vs2010 win7 下开发视频服务器,用到ffmpeg,debug版本运行正常,切换到release时,出现"0x00905a4d 处未处理的异常: 0xC0000005: 读取位置 0 ...

  6. eclipse安卓模拟器Failed to install on device 'emulator-5554': timeout处理方案

    我们在用模拟器调试的时候,经常会出现Failed to install on device 'emulator-5554': timeout这个错误.其实就是有些虚拟器在部署的时候时间过于长.系统就认 ...

  7. java.lang.Class.getDeclaredMethod()方法详解

    Java.lang.Class.getDeclaredMethod()方法用法 注:方法返回一个Method对象,它反映此Class对象所表示的类或接口的指定已声明方法. 描述 java.lang.C ...

  8. 每天一道算法题目(18)——取等长有序数组的上中位数和不等长有序数组的第k小的数

    1.取上中位数 题目: 给定两个有序数组arr1和arr2,两个数组长度都为N,求两个数组中所有数的上中位数.要求:时间复杂度O(logN).      例如:          arr1 = {1, ...

  9. PCL—点云分割(超体聚类) 低层次点云处理

    博客转载自:http://www.cnblogs.com/ironstark/p/5013968.html 1.超体聚类——一种来自图像的分割方法 超体(supervoxel)是一种集合,集合的元素是 ...

  10. JavaWeb_内省(Instrospector)

    内省是什么? 开发框架时,经常需要使用java对象的属性来封装程序的数据,每次都是用反射技术完成此类操作过于麻烦,所以sun公司开发了一套API,专门用于操作Java对象的属性. 什么是Java对象的 ...