到了这里,终于进入ci框架的核心了。既然是“引导”文件,那么就是对用户的请求、参数等做相应的导向,让用户请求和数据流按照正确的线路各就各位。例如,用户的请求url:
http://you.host.com/usr/reg
经过引导文件,实际上会交给application中的usrcontroller控制器的reg方法去处理。 这之中,codeigniter.php做了哪些工作?我们一步步来看。
1. 导入预定义常量、框架环境初始化 之前的一篇博客(ci框架源码阅读笔记2 一切的入口 index.php)中,我们已经看到,index.php文件中已经对框架的environment,application,system等做了定义和安全性检查.
(1). 加载预定义常量constants.
如果定义了环境,且针对该环境的预定义常量文件存在,则优先加载环境的常量定义文件,否则加载config目录下的常量定义文件:
if (defined('environment') and file_exists(apppath.'config/'.environment.'/constants.php')) { require(apppath.'config/'.environment.'/constants.php'); } else{ require(apppath.'config/constants.php'); }
这么做的原因,我们之前已经介绍过了,可以快速切换环境和相应参数而不必更改应用程序核心代码。
(2). 设置自定义错误处理函数。
这里是_exception_handler函数,该函数的定义和解释见上一篇博客( http://www.cnblogs.com/ohmygirl/p/ciread-3.html)。再次引用手册中一句话,提醒大家注意:以下级别的错误不能由用户定义的函数来处理: e_error、 e_parse、 e_core_error、 e_core_warning、 e_compile_error、 e_compile_warning,和在 调用 set_error_handler() 函数所在文件中产生的大多数 e_strict。
(3). 检查核心class是否被扩展
if (isset($assign_to_config['subclass_prefix']) and $assign_to_config['subclass_prefix'] != '') { get_config(array('subclass_prefix' => $assign_to_config['subclass_prefix'])); }
其中,$assign_to_config应该是定义在入口文件index.php中的配置数组. 通常情况下,ci的核心组件的名称均以”ci_”开头,而如果更改了或者扩展ci的核心组件,则应该使用不同的subclass_prefix前缀如my_ ,这种情况下,应该通过$assign_to_config[‘subclass_prefix’]指定你的扩展核心的前缀名,便于ci的loader组件加载该类,或者可能出现找不到文件的错误。另外,subclass_prefix配置项默认是位于apppath/config/config.php配置文件中的,这段代码同样告诉我们,index.php文件中的subclass_prefix具有更高的优先权(也就是,如果两处都设置了subclass_prefix,index.php中的配置项会覆盖配置文件config.php中的配置)。
到这里,ci框架的基本环境配置初始化已经算是完成了,接下来,codeigniter会借助一系列的组件,完成更多的需求。
2. 加载核心组件 通常,ci框架中不同的功能均由不同的组件来完成(如log组件主要用于记录日志,input组件则用于处理用户的get,post等数据)这种模块化的方式使得各组件之间的耦合性较低,从而也便于扩展。ci中主要的核心组件如下所示:
其中:
bm: 指benchmark,是ci的基准点组件,主要用于mark各种时间点、记录内存使用等参数,便于性能测试和追踪。
ext: ci的扩展组件,前面已经介绍过,用于在不改变ci核心的基础上改变或者增加系统的核心运行功能。hook钩子允许你在系统运行的各个挂钩点(hook point)添加自定义的功能和跟踪,如pre_system,pre_controller,post_controller等预定义的挂钩点。以下所有的$ext->_call_hook(xxx);均是call特定挂钩点的程序(如果有的话)。
cfg: config配置管理组件。主要用于加载配置文件、获取和设置配置项等。
uni: 用于对utf-8字符集处理的相关支持。其他组件如input组件,需要改组件的支持。
uri: 解析uri(uniform rescource identifier)参数等.这个组件与rtr组件关系紧密。(似乎uri与router走到哪里都是好基友)。
rtr: 路由组件。通过uri组件的参数解析,决定数据流向(路由)。
out: 最终的输出管理组件,掌管着ci的最终输出(海关啊)。
sec: 安全处理组件。毕竟安全问题永远是一个大问题。
以bm组件为例,核心组件的加载方式是:
$bm =& load_class('benchmark', 'core');
调用了load_class函数获取core目录下的相应组件。(load_class的实现和具体介绍见之前的博客:ci框架源码阅读笔记3 全局函数common.php)
各组件的功能和具体实现之后会有详细的分析, 这里我们只需要知道该组件的基本功能即可。
3. 设置路由。 调用很简单,只有一句话:
$rtr->_set_routing();
调用router组件的_set_routing()函数来设置路由,具体的实现细节,我们这里暂且不管(之后的部分会有详细介绍),我们只需要知道,通过_set_routing的处理,我们可以获得实际请求的controller,uri的segment参数段等信息。
值得注意的是,ci允许在index.php中配置routing,且会覆盖默认的routing设置(如共享ci的安装目录的多个应用程序可能有不同的routing):
if (isset($routing)) { $rtr->_set_overrides($routing); }
设置完路由之后,可以通过该组件的:fetch_diretory() , fetch_class(), fetch_method()等分别获取目录、类、和方法。
4. 检查缓存 到了这一步,ci会先检查是否有cache_override这个钩子(默认情况下没有配置,也就是返回false),如果没有注册,则调用_display_cache方法输出缓存(这种说法并不准确,准确来说应该是,如果有相应的缓存,则输出缓存且直接退出程序,否则返回false,这里我们暂时不去思考实现细节):
if ($ext->_call_hook('cache_override') === false) { if ($out->_display_cache($cfg, $uri) == true) { exit; } }
5. 实例化控制器,安全性验证、实际处理请求。 能够走到这里,说明之前的缓存是没有命中的(实际上,任何页面都是应该先走到这一步,然后才会有设置缓存,之后的访问检查缓存才会命中)。这一步会require controller基类和扩展的controller类(如果有的话)及实际的应用程序控制器类:
require basepath.'core/controller.php'; if (file_exists(apppath.'core/'.$cfg->config['subclass_prefix'].'controller.php')) { require apppath.'core/'.$cfg->config['subclass_prefix'].'controller.php'; } if ( ! file_exists(apppath.'controllers/'.$rtr->fetch_directory().$rtr->fetch_class().'.php')) { show_error('xxx'); } include(apppath.'controllers/'.$rtr->fetch_directory().$rtr->fetch_class().'.php');
之前我们已经说过,在router组件_set_routing之后,可以通过fetch_directory(), fetch_class(), fetch_method()等分别获取请求的文件目录、控制器和方法。
现在对请求的控制器和方法做验证,我们看一下ci的主要验证:
if ( ! class_exists($class) or strncmp($method, '_', 1) == 0 or in_array(strtolower($method), array_map('strtolower', get_class_methods('ci_controller'))) )
这里简单解释一下,ci认为不合法的情况有:
(1).请求的class不存在:! class_exists($class)
(2).请求的方法以_开头(被认为是私有的private的方法,之所以这么做事因为php并不是一开始就支持private,public的访问权限的):strncmp($method, '_', 1) == 0
(3).基类ci_controller中的方法不能直接被访问:in_array(strtolower($method), array_map('strtolower', get_class_methods('ci_controller'))
如果请求的条件满足上面3个中的任何一个,则被认为是不合法的请求(或者是无法定位的请求),因此会被ci定向到404页面(值得注意的是,如果设置了404_override,并且404_override的class存在,并不会直接调用show_404并退出,而是会像正常的访问一样,实例化:$ci = new $class();)
走到这里,ci的controller总算是加载完了(累趴)。不过且慢,还有不少事情要做:
(1). 检查_remap。
_remap这个东西类似于ci的rewrite,可以将你的请求定位到其他的位置。这个方法是应该定义在你的应用程序控制器的:
public function _remap($method){ $this->index(); }
现在,所有的请求都会被定位到改控制器的index()中去了。如果_remap不存在,则调用实际控制器的$method方法:
call_user_func_array(array(&$ci, $method), array_slice($uri->rsegments, 2));
(2).最终输出
$this->load->view()之后,并不会直接输出,而是放在了缓存区。$out->_display之后,才会设置缓存,并最终输出(详细参考output.php和loader.php)
(3)若有使用了数据库,还要关闭数据库连接:
if (class_exists('ci_db') and isset($ci->db)) { $ci->db->close(); }
注意,如果在config/database.php中设置了开启pconnect,则建立的连接是长连接,这种长连接是不会被close关闭的。所以,请谨慎使用pconnect.
到现在,ci的核心流程总算是走完了(虽然还有很多细节的问题,但不管怎么说,大树的枝干已经有了,树叶的细节,可以慢慢添加)。在结束本文之前,我们来梳理一下ci的核心执行流程:
回顾之前我们引用的官方给出的流程图,是不是基本一致的:
最后,贴上整个文件的源码:
$assign_to_config['subclass_prefix'])); }/* * set a liberal script execution time limit */ //if(function_exists(set_time_limit) && @!ini_get(safe_mode)) if (function_exists(set_time_limit) == true and @ini_get(safe_mode) == 0) { @set_time_limit(300); } $bm =& load_class('benchmark', 'core'); $bm->mark('total_execution_time_start'); $bm->mark('loading_time:_base_classes_start');/* * instantiate the hooks class */ $ext =& load_class('hooks', 'core');/* * is there a pre_system hook? */ $ext->_call_hook('pre_system');/* * instantiate the config class */ $cfg =& load_class('config', 'core'); // do we have any manually set config items in the index.php file? if (isset($assign_to_config)) { $cfg->_assign_to_config($assign_to_config); }/* * instantiate the utf-8 class */ $uni =& load_class('utf8', 'core');/* * ------------------------------------------------------ * instantiate the uri class * ------------------------------------------------------ */ $uri =& load_class('uri', 'core');/* * instantiate the routing class and set the routing */ $rtr =& load_class('router', 'core'); $rtr->_set_routing(); // set any routing overrides that may exist in the main index file if (isset($routing)) { $rtr->_set_overrides($routing); }/* * instantiate the output class */ $out =& load_class('output', 'core');/* * is there a valid cache file? if so, we're done... */ if ($ext->_call_hook('cache_override') === false) { if ($out->_display_cache($cfg, $uri) == true) { exit; } }/* * load the security class for xss and csrf support */ $sec =& load_class('security', 'core');/* * load the input class and sanitize globals */ $in =& load_class('input', 'core');/* * load the language class */ $lang =& load_class('lang', 'core');/* * load the app controller and local controller */ // load the base controller class require basepath.'core/controller.php'; function &get_instance() { return ci_controller::get_instance(); } if (file_exists(apppath.'core/'.$cfg->config['subclass_prefix'].'controller.php')) { require apppath.'core/'.$cfg->config['subclass_prefix'].'controller.php'; } if ( ! file_exists(apppath.'controllers/'.$rtr->fetch_directory().$rtr->fetch_class().'.php')) { show_error('unable to load your default controller. please make sure the controller specified in your routes.php file is valid.'); } include(apppath.'controllers/'.$rtr->fetch_directory().$rtr->fetch_class().'.php'); $bm->mark('loading_time:_base_classes_end');/* * security check */ $class = $rtr->fetch_class(); $method = $rtr->fetch_method(); if ( ! class_exists($class) or strncmp($method, '_', 1) == 0 or in_array(strtolower($method), array_map('strtolower', get_class_methods('ci_controller'))) ) { if ( ! empty($rtr->routes['404_override'])) { $x = explode('/', $rtr->routes['404_override']); $class = $x[0]; $method = (isset($x[1]) ? $x[1] : 'index'); if ( ! class_exists($class)) { if ( ! file_exists(apppath.'controllers/'.$class.'.php')) { show_404({$class}/{$method}); } include_once(apppath.'controllers/'.$class.'.php'); } } else { show_404({$class}/{$method}); } }/* * is there a pre_controller hook? */ $ext->_call_hook('pre_controller');/* * instantiate the requested controller */ // mark a start point so we can benchmark the controller $bm->mark('controller_execution_time_( '.$class.' / '.$method.' )_start'); $ci = new $class();/* * is there a post_controller_constructor hook? */ $ext->_call_hook('post_controller_constructor');/* * call the requested method */ // is there a remap function? if so, we call it instead if (method_exists($ci, '_remap')) { $ci->_remap($method, array_slice($uri->rsegments, 2)); } else { if ( ! in_array(strtolower($method), array_map('strtolower', get_class_methods($ci)))) { if ( ! empty($rtr->routes['404_override'])) { $x = explode('/', $rtr->routes['404_override']); $class = $x[0]; $method = (isset($x[1]) ? $x[1] : 'index'); if ( ! class_exists($class)) { if ( ! file_exists(apppath.'controllers/'.$class.'.php')) { show_404({$class}/{$method}); } include_once(apppath.'controllers/'.$class.'.php'); unset($ci); $ci = new $class(); } } else { show_404({$class}/{$method}); } } call_user_func_array(array(&$ci, $method), array_slice($uri->rsegments, 2)); } // mark a benchmark end point $bm->mark('controller_execution_time_( '.$class.' / '.$method.' )_end');/* * is there a post_controller hook? */ $ext->_call_hook('post_controller');/* * send the final rendered output to the browser */ if ($ext->_call_hook('display_override') === false) { $out->_display(); }/* * is there a post_system hook? */ $ext->_call_hook('post_system');/* * close the db connection if one exists */ if (class_exists('ci_db') and isset($ci->db)) { $ci->db->close(); }/* end of file codeigniter.php */
由于写作仓促,难免会有错误。欢迎随时指出,欢迎沟通交流。