1.概述
CodeIgniter.php是整个框架的核心文件,这个文件的官方注释是
/**
* System Initialization File
* 系统初始化文件
* Loads the base classes and executes the request.
* 加载基础类并执行请求
*/
CodeIgniter除了加载系统核心类,还要将客户端的请求传递到对应的控制器中,并且要将控制器返回的内容响应给客户端。
2. 阅读源码
BASEPATH常量是在index.php中定义的,这里判断BASEPATH是否存在是防止用户绕过入口直接返回文件
defined('BASEPATH') OR exit('No direct script access allowed');
版本号
const CI_VERSION = '3.1.3';
加载框架内预定义常量
if (file_exists(APPPATH.'config/'.ENVIRONMENT.'/constants.php'))
{
require_once(APPPATH.'config/'.ENVIRONMENT.'/constants.php');
}
if (file_exists(APPPATH.'config/constants.php'))
{
require_once(APPPATH.'config/constants.php');
}
加载公共类——公共类中封装了很多常用功能函数
require_once(BASEPATH.'core/Common.php');
进行系统安全检测
//php版本若小于5.4
if ( ! is_php('5.4'))
{
//magic_quotes_runtime设置为关闭
//magic_quotes_runtime含义:运行时从外部资源产生的数据是否使用自动字符串转义
ini_set('magic_quotes_runtime', 0);
//register_globals配置是否打开,因为register_globals存在安全隐患,如果此配置打开则将删除预定义全局变量
if ((bool) ini_get('register_globals'))
{
$_protected = array(
'_SERVER',
'_GET',
'_POST',
'_FILES',
'_REQUEST',
'_SESSION',
'_ENV',
'_COOKIE',
'GLOBALS',
'HTTP_RAW_POST_DATA',
'system_path',
'application_folder',
'view_folder',
'_protected',
'_registered'
);
$_registered = ini_get('variables_order');
foreach (array('E' => '_ENV', 'G' => '_GET', 'P' => '_POST',
'C'=>'_COOKIE', 'S' => '_SERVER') as $key=>$superglobal)
{
if (strpos($_registered, $key) === FALSE)
{
continue;
}
foreach (array_keys($$superglobal) as $var)
{
if (isset($GLOBALS[$var]) && ! in_array($var, $_protected, TRUE))
{
$GLOBALS[$var] = NULL;
}
}
}
}
}
使用框架的异常处理程序来处理异常
set_error_handler('_error_handler');
set_exception_handler('_exception_handler');
register_shutdown_function('_shutdown_handler');
assign_to_config是在Index文件中定义的变量,如果这个变量不为空,则把数组内容赋值到配置参数中去,从这里也可以看得出,assign_to_config的配置优先级是高于配置文件的
if ( ! empty($assign_to_config['subclass_prefix']))
{
get_config(array('subclass_prefix' => $assign_to_config['subclass_prefix']));
}
加载Benchmark组件:
Benchmark可以监测和跟踪性能,total_execution_time_start,loading_time:_base_classes_start标记的是当前时间,精确到毫秒
$BM =& load_class('Benchmark', 'core');
$BM->mark('total_execution_time_start');
$BM->mark('loading_time:_base_classes_start');
加载Hooks组件:
Hooks组件可以在不改变系统代码的情况下增加扩展功能,扩展方式就是在系统某个位置放置挂钩点,例如:pre_system,就是放置在系统中的挂钩点
$EXT =& load_class('Hooks', 'core');
$EXT->call_hook('pre_system');
加载Config组件:
Config组件是对配置参数进行管理,如果assign_to_config变量不为空,则将其值添加到配置参数中
$CFG =& load_class('Config', 'core');
if (isset($assign_to_config) && is_array($assign_to_config))
{
foreach ($assign_to_config as $key => $value)
{
$CFG->set_item($key, $value);
}
}
设置字符串编码格式
//创建一个变量保存配置文件中声明的字符串编码格式
$charset = strtoupper(config_item('charset'));
//将配置的编码格式设置为默认编码格式
ini_set('default_charset', $charset);
//如果mbstring扩展打开(mbstring是字符串编码和字符串处理库)
if (extension_loaded('mbstring'))
{
//声明一个全局常量保存mbstring扩展状态
define('MB_ENABLED', TRUE);
//设置编码格式为$charset
@ini_set('mbstring.internal_encoding', $charset);
//若输出的字符不存在编码格式中,则用none替代
mb_substitute_character('none');
}
else
{
//声明一个全局常量保存mbstring扩展状态
define('MB_ENABLED', FALSE);
}
//如果iconv扩展打开(iconv是字符集之间转换的库)
if (extension_loaded('iconv'))
{
//声明一个全局常量保存mbstring扩展状态
define('ICONV_ENABLED', TRUE);
//设置编码格式为$charset
@ini_set('iconv.internal_encoding', $charset);
}
else
{
//声明一个全局常量保存mbstring扩展状态
define('ICONV_ENABLED', FALSE);
}
//如果php版本高于5.6
if (is_php('5.6'))
{
//设置编码格式为$charset
ini_set('php.internal_encoding', $charset);
}
引入mbstring,hash,password,standard兼容性处理的文件
require_once(BASEPATH.'core/compat/mbstring.php');
require_once(BASEPATH.'core/compat/hash.php');
require_once(BASEPATH.'core/compat/password.php');
require_once(BASEPATH.'core/compat/standard.php');
提供Utf8支持的组件
$UNI =& load_class('Utf8', 'core');
解析URI参数的组件
$URI =& load_class('URI', 'core');
路由组件,通过解析出来的URI参数,决定数据路由方向
$RTR =& load_class('Router', 'core', isset($routing) ? $routing : NULL);
输出组件,通过output输出最终内容
$OUT =& load_class('Output', 'core');
cache_override是一个挂钩点,这个挂钩点可以自定义缓存策略,如果没有扩展这个钩子,查看输出组件是否有缓存,有缓存则直接输出
if ($EXT->call_hook('cache_override') === FALSE && $
OUT->_display_cache($CFG, $URI) === TRUE)
{
exit;
}
Security:安全组件,可以防范xss,csrf等网络攻击
Input:输入组件,可通过input读取客户端输入,表单提交等数据
Lang:加载语言类型的组件
$SEC =& load_class('Security', 'core');
$IN =& load_class('Input', 'core');
$LANG =& load_class('Lang', 'core');
加载控制器基类(缓存没有命中才会执行到这一步)
require_once BASEPATH.'core/Controller.php';
//get_instance指针函数返回控制器单例指针
function &get_instance()
{
return CI_Controller::get_instance();
}
//如果有自定义的控制器基类,一并加载进来
if (file_exists(APPPATH.'core/'.$CFG->config['subclass_prefix'].'Controller.php'))
{
require_once APPPATH.'core/'.$CFG->config['subclass_prefix'].'Controller.php';
}
//标记时间点
$BM->mark('loading_time:_base_classes_end');
还记得前面加载的URI和RTR组件吗?下面这段代码就是通过URI解析的参数,按照RTR的路由规则,实例化对应的控制器来处理数据请求
//404标记变量
$e404 = FALSE;
//路由类名
$class = ucfirst($RTR->class);
//路由方法名
$method = $RTR->method;
//如果类名为空或者找不到路由类名的文件,404
if (empty($class) OR
! file_exists(APPPATH.'controllers/'.$RTR->directory.$class.'.php'))
{
$e404 = TRUE;
}
else
{
//加载路由类名文件
require_once(APPPATH.'controllers/'.$RTR->directory.$class.'.php');
//类如果不存在,路由方法为私有函数,方法名在CI_Controller中已存在,404
if ( ! class_exists($class, FALSE) OR
$method[0] === '_' OR method_exists('CI_Controller', $method))
{
$e404 = TRUE;
}
//控制器中存在_remap映射方法,将参数和方法名传给_remap映射方法中
elseif (method_exists($class, '_remap'))
{
$params = array($method, array_slice($URI->rsegments, 2));
$method = '_remap';
}
//方法名不存在,404
elseif ( ! method_exists($class, $method))
{
$e404 = TRUE;
}
//当前作用域是否可以调用路由方法
elseif ( ! is_callable(array($class, $method)))
{
$reflection = new ReflectionMethod($class, $method);
//不是公开方法或是构造方法,404
if ( ! $reflection->isPublic() OR $reflection->isConstructor())
{
$e404 = TRUE;
}
}
}
//路由路径不存在,展示404
if ($e404)
{
//如果有配置404页面路由参数
if ( ! empty($RTR->routes['404_override']))
{
//解析路由配置文件定义的404_override(config/route这个配置文件中如果配置
//404_override参数的话,需要按照%[^/]/%s的字符串格式进行配置才能被正确解
//析:如Err_code/code404则会找到Err_code类,code404方法)
if (sscanf($RTR->routes['404_override'], '%[^/]/%s',
$error_class, $error_method) !== 2)
{
$error_method = 'index';
}
$error_class = ucfirst($error_class);
if ( ! class_exists($error_class, FALSE))
{
if (file_exists(APPPATH.'controllers/'.
$RTR->directory.$error_class.'.php'))
{
require_once(APPPATH.'controllers/'.
$RTR->directory.$error_class.'.php');
$e404 = ! class_exists($error_class, FALSE);
}
elseif ( ! empty($RTR->directory) &&
file_exists(APPPATH.'controllers/'.$error_class.'.php'))
{
require_once(APPPATH.'controllers/'.$error_class.'.php');
if (($e404 = ! class_exists($error_class, FALSE)) === FALSE)
{
$RTR->directory = '';
}
}
}
else
{
$e404 = FALSE;
}
}
//找到自定义的404页面
if ( ! $e404)
{
$class = $error_class;
$method = $error_method;
$URI->rsegments = array(
1 => $class,
2 => $method
);
}
else
{
show_404($RTR->directory.$class.'/'.$method);
}
}
//提取出传递参数
if ($method !== '_remap')
{
$params = array_slice($URI->rsegments, 2);
}
挂钩点pre_controller
$EXT->call_hook('pre_controller');
标记当前时间,实例化路由类
$BM->mark('controller_execution_time_( '.$class.' / '.$method.' )_start');
$CI = new $class();
挂钩点post_controller_constructor
$EXT->call_hook('post_controller_constructor');
调用路由方法
call_user_func_array(array(&$CI, $method), $params);
输出最终数据
//标记当前时间
$BM->mark('controller_execution_time_( '.$class.' / '.$method.' )_end');
//挂钩点post_controller
$EXT->call_hook('post_controller');
//display_override是否有钩子,没有,输出最终内容
if ($EXT->call_hook('display_override') === FALSE)
{
$OUT->_display();
}
//挂钩点post_system
$EXT->call_hook('post_system');
到这里CodeIgniter.php的源码就阅读完了,回顾整个代码流程,有没有发现跟官方的流程图有点相似: