上篇解析入口文件写到载入 core/CodeIgniter.php框架核心文件,启动框架。CodeIgniter.php文件被称为BOOTSTRAP,也就是引导文件,这里也就是CI框架的核心了。其实把CodeIgniter.php这个文件的代码运行一次,就是整个CI应用都完成了一次完整的运作流程了。其中会加载一些组件,引入很多外部文件,等等。所以建议在阅读此文件代码的时候,第一遍先阅读它的大概流程,也就是说不必进入相应的组件、函数文件中去。第二遍看的时候才具体看那些函数、组件里面是怎么实现的。这个当然要看个人需要咯~
通过解析,博主总结CodeIgniter.php文件一共完成已下几种工作:
① 加载框架常量、函数库以及框架初始化
② 加载核心类组件
③ 路由的设置与判断
④ 解析请求的类,并调用请求的方法
⑤ 输出
1、加载框架常量、函数库以及框架初始化
1) 执行前工作
判断常量、设置版本号等。
/** * 这个BASEPATH,就是在入口文件(index.php)里面定义的那个BASEPATH~ * 如果没有定义BASEPATH,那么直接退出,下面程序都不执行。 * 其实除了入口文件index.php开头没有这句话之外,所有文件都会有这句话 * 也就是说,所有文件都不能单独运行,一定是index.php在运行过程中把这些文件通 * 过某种方式引进来运行,所以只有入口文件index.php才能被访问。 */ defined('BASEPATH') OR exit('No direct script access allowed'); //设置版本号 define('CI_VERSION', '3.1.0');
2) 加载框架常量
if (file_exists(APPPATH . 'config/' . ENVIRONMENT . '/constants.php')) { require_once(APPPATH . 'config/' . ENVIRONMENT . '/constants.php'); } require_once(APPPATH . 'config/constants.php');
加载配置的常量。这个配置文件里面默认已经有一些和文件有关的常量。下面这个判断可以看出一开始我们在index.php里面定义的那个ENVIRONMENT的作用之一,如果是定义某个环境,会调用相应的配置文件,这样就可以使得应用在相应的环境中运行。不仅仅是这个常量的配置文件是这样子,以后你会发现,其实全部配置文件都是先判断当前环境再引入。方便切换,只需在index.php里面改一下ENVIRONMENT的值。
当然啦,如果压根没有这个环境下的配置文件,就会调用默认的。CI手册上也有说,各种环境下的相同的配置文件,可以直接放在/config/下,而不需要每个环境的目录下都有。
3) 加载全局函数库
//加载全局函数库 require_once(BASEPATH . 'core/Common.php');
4) 进行全局变量安全处理
if (!is_php('5.4')) { //上篇博文已经讲述过ini_set()、ini_get()函数的功能 ini_set('magic_quotes_runtime', 0); 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; } } } } }
如果低于php5.4版本,将进行全局变量安全处理。当开启了register_globals,这就意味着EGPCS中的变量可以直接用变量名访问,这些全局变量是存储在$GLOBALS数组中的,这是个隐患,虽然5.4及之后消除了,但考虑兼容以前,需要手工清除这些全局变量。那么挑选了最重要的需要特别保护的一些变量名,也就是$_protected数组的值。凡是EGPCS中涉及到变量名称在$_protected数组中的,一律清空。
5) 自定义错误、异常和程序完成的函数
set_error_handler('_error_handler'); set_exception_handler('_exception_handler'); register_shutdown_function('_shutdown_handler');
a、设置错误处理:set_error_handler('_error_handler')。处理函数原型:function _error_handler($severity, $message, $filepath, $line)。程序本身原因或手工触发trigger_error("A custom error has been triggered");
b、设置异常处理:set_exception_handler('_exception_handler')。处理函数原型:function _exception_handler($exception)。当用户抛出异常时触发throw new Exception('Exception occurred');
c、千万不要被shutdown迷惑:register_shutdown_function('_shutdown_handler')可以这样理解调用条件:当页面被用户强制停止时、当程序代码运行超时时、当php代码执行完成时。
6) 检查核心class是否被扩展
if (!empty($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中的配置)。
7) 加载composer
注:这个内容比较大,知识比较多,以后有机会单独开篇来介绍。//加载composer if ($composer_autoload = config_item('composer_autoload')) { if ($composer_autoload === TRUE) { file_exists(APPPATH . 'vendor/autoload.php') ? require_once(APPPATH . 'vendor/autoload.php') : log_message('error', '$config[\'composer_autoload\'] is set to TRUE but ' . APPPATH . 'vendor/autoload.php was not found.'); } elseif (file_exists($composer_autoload)) { require_once($composer_autoload); } else { log_message('error', 'Could not find the specified $config[\'composer_autoload\'] path: ' . $composer_autoload); } }
到这里,CI框架的基本环境配置初始化已经算是完成了,接下来,CodeIgniter会借助一系列的组件,完成更多的需求。
2、加载核心类组件
通常,CI框架中不同的功能均由不同的组件来完成(如Log组件主要用于记录日志,Input组件则用于处理用户的GET,POST等数据)这种模块化的方式使得各组件之间的耦合性较低,从而也便于扩展。CI中主要的核心组件如下所示:
a) BM:指BenchMark,是CI的基准点组件,主要用于mark各种时间点、记录内存使用等参数,便于性能测试和追踪。
b) 钩子类->EXT:CI的扩展组件,前面已经介绍过,用于在不改变CI核心的基础上改变或者增加系统的核心运行功能。Hook钩子允许你在系统运行的各个挂钩点(hook point)添加自定义的功能和跟踪,如pre_system,pre_controller,post_controller等预定义的挂钩点。以下所有的$EXT->_call_hook("xxx");均是call特定挂钩点的程序(如果有的话)。
c) 配置类->CFG:Config配置管理组件。主要用于加载配置文件、获取和设置配置项等。
d) utf8类->UNI:用于对UTF-8字符集处理的相关支持。其他组件如INPUT组件,需要改组件的支持。
e) URL类->URI:解析URI(Uniform Rescource Identifier)参数等.这个组件与RTR组件关系紧密。(似乎URI与Router走到哪里都是好基友)。
f) 路由类->RTR:路由组件。通过URI组件的参数解析,决定数据流向(路由)。
g) OUTPUT类->OUT:最终的输出管理组件,掌管着CI的最终输出(海关啊)。
h) 安全类->SEC:安全处理组件。毕竟安全问题永远是一个大问题。
i) 输入及过滤类->IN:用于获取输入以及表单验证。
j) 语言类->LANG:用于设置框架语言。
3、路由的设置与判断
<pre name="code" class="php"> $e404 = FALSE; $class = ucfirst($RTR->class); $method = $RTR->method; if (empty($class) OR !file_exists(APPPATH . 'controllers/' . $RTR->directory . $class . '.php')) { $e404 = TRUE; } else { require_once(APPPATH . 'controllers/' . $RTR->directory . $class . '.php'); if (!class_exists($class, FALSE) OR $method[0] === '_' OR method_exists('CI_Controller', $method)) { $e404 = TRUE; } elseif (method_exists($class, '_remap')) { $params = array($method, array_slice($URI->rsegments, 2)); $method = '_remap'; } elseif (!in_array(strtolower($method), array_map('strtolower', get_class_methods($class)), TRUE)) { $e404 = TRUE; } }
CI认为下面这几种情况认为是404,如果找不到就调用show_404()函数: 1) 请求的class不存在:! class_exists($class) 2) 请求私有方法:!$method[0] === '_' 3) 请求基类方法:method_exists('CI_Controller', $method) 4)请求的方法不存在:! in_array(strtolower($method), array_map('strtolower', get_class_methods($class)), TRUE)
如果请求的条件满足上面3个中的任何一个,则被认为是不合法的请求(或者是无法定位的请求),因此会被CI定向到404页面(值得注意的是,如果设置了404_override,并且404_override的class存在,并不会直接调用show_404并退出,而是会像正常的访问一样,实例化:$CI = new $class();)
其中有一段代码是关于404处理,在这里就不把代码贴上来了,在文章的最后我会将整个文件的代码(注释版)贴出来。 走到这里,CI的Controller总算是加载完了(累趴)。不过且慢,还有不少事情要做:
if ($method !== '_remap') { $params = array_slice($URI->rsegments, 2); }
检查_remap,_remap这个东西类似于CI的rewrite,可以将你的请求定位到其他的位置。这个方法是应该定义在你的应用程序控制器的;现在,所有的请求都会被定位到改控制器的index()中去了。如果_remap不存在,则调用实际控制器的$method方法:call_user_func_array(array(&$CI, $method), $params);
4、解析请求的类,并调用请求的方法
$CI = new $class(); //钩子,不想多说了 $EXT->call_hook('post_controller_constructor'); call_user_func_array(array(&$CI, $method), $params);
call_user_func_array 调用回调函数,并把一个数组参数作为回调函数的参数,call_user_func_array 函数和 call_user_func 很相似,只是 使 用了数组 的传递参数形式,让参数的结构更清晰。
5、输出
if ($EXT->call_hook('display_override') === FALSE) { //这里,把$this->load->view();里面缓冲的输出结果输出,基本上一个流程总算完成了。 $OUT->_display(); }
最终输出,$this->load->view()之后,并不会直接输出,而是放在了缓存区。$Out->_display之后,才会设置缓存,并最终输出。
至此,终于可以松一口气了。到现在,CI的核心流程总算是走完了(虽然还有很多细节的问题,但不管怎么说,大树的枝干已经有了,树叶的细节,可以慢慢添加)。在结束本文之前,我们来梳理一下CI的核心执行流程:
最后,贴一下整个CodeIgniter.php文件的源码(注释版):
<?php /** * ======================================= * Created by Pocket Knife Technology. * User: ZhiHua_W * Date: 2016/10/15 0032 * Time: 上午 9:14 * Project: CodeIgniter框架—源码分析 * Power: Analysis for CodeIgniter.php * ======================================= */ /** * 这个BASEPATH,就是在入口文件(index.php)里面定义的那个BASEPATH~ * 如果没有定义BASEPATH,那么直接退出,下面程序都不执行。 * 其实除了入口文件index.php开头没有这句话之外,所有文件都会有这句话 * 也就是说,所有文件都不能单独运行,一定是index.php在运行过程中把这些文件通 * 过某种方式引进来运行,所以只有入口文件index.php才能被访问。 */ defined('BASEPATH') OR exit('No direct script access allowed'); //设置版本号 define('CI_VERSION', '3.1.0'); /** * 加载框架常量:这个配置文件里面默认已经有一些和文件有关的常量 * 根据定义的环境,加载对应的环境目录下的常量, * 如果与系统常量冲突,最终以系统常量为准,所以环境常量无法覆盖系统常量。 * 这样做主要为了快速设置特定环境下的特定常量。 */ if (file_exists(APPPATH . 'config/' . ENVIRONMENT . '/constants.php')) { require_once(APPPATH . 'config/' . ENVIRONMENT . '/constants.php'); } require_once(APPPATH . 'config/constants.php'); //加载全局函数库 require_once(BASEPATH . 'core/Common.php'); //进行全局变量安全处理 if (!is_php('5.4')) { //上篇博文已经讲述过ini_set()、ini_get()函数的功能 ini_set('magic_quotes_runtime', 0); /** * 此处存在的知识点 * PHP变量解析顺序:ini_get('variables_order'),同时也声明了接收哪种类型发送过来的变量; * 当程序中使用了$_REQUEST接收变量,设置顺序EGPCS(Environment,GET,POST,Cookie,Server)就很重要,注意是从右向左覆盖。 * php配置文件给出了配置提示 * Default Value: "EGPCS"; * Development Value: "GPCS"; * Production Value: "GPCS"; * PHP 5.4.0 废除了register_globals,magic_quotes以及安全模式。因此这一段是专门针对PHP5.4之前的版本的。 * 当开启了register_globals,这就意味着EGPCS中的变量可以直接用变量名访问, * 这些全局变量是存储在$GLOBALS数组中的,这是个隐患,虽然5.4及之后消除了,但考虑兼容以前, * 需要手工清除这些全局变量。那么挑选了最重要的需要特别保护的一些变量名, * 也就是$_protected数组的值。凡是EGPCS中涉及到变量名称在$_protected数组中的,一律清空。 */ 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; } } } } } /** * 自定义错误、异常和程序完成的函数 * 1、设置错误处理:set_error_handler('_error_handler')。处理函数原型:function _error_handler($severity, $message, $filepath, $line)。 * 程序本身原因或手工触发trigger_error("A custom error has been triggered"); * 2、设置异常处理:set_exception_handler('_exception_handler')。处理函数原型:function _exception_handler($exception)。 * 当用户抛出异常时触发throw new Exception('Exception occurred'); * 3、千万不要被shutdown迷惑:register_shutdown_function('_shutdown_handler')可以这样理解调用条件:当页面被用户强制停止时、当程序代码运行超时时、当php代码执行完成时。 */ set_error_handler('_error_handler'); set_exception_handler('_exception_handler'); register_shutdown_function('_shutdown_handler'); /** * 这个subclass_prefix是什么? * 这是一个非常棒的东西!!有了它我们就可以轻易扩展CI框架~ * 具体core/Common.php 中的load_class() */ if (!empty($assign_to_config['subclass_prefix'])) { //这个get_config($replace)就是从配置文件里面读取信息,这里是读取config/config.php中的配置信息 //这个参数$replace的作用是什么呢?就是临时把修改配置文件的意思,注意并没有从改变文件的值,这个改变只是 //停留在内存的层面上。 //而$assign_to_config['xxx'];是在index.php中定义的一个配置信息数组,这个配置数组要优先权要大于配置文件当中的。 //所以这个判断的作用是,看看有没有在index.php里面定义了 $assign_to_config['subclass_prefix'],如果有的话, //就那把配置文件中的$config['subclass_prefix']的值改成这个。 get_config(array('subclass_prefix' => $assign_to_config['subclass_prefix'])); } //加载composer if ($composer_autoload = config_item('composer_autoload')) { if ($composer_autoload === TRUE) { file_exists(APPPATH . 'vendor/autoload.php') ? require_once(APPPATH . 'vendor/autoload.php') : log_message('error', '$config[\'composer_autoload\'] is set to TRUE but ' . APPPATH . 'vendor/autoload.php was not found.'); } elseif (file_exists($composer_autoload)) { require_once($composer_autoload); } else { log_message('error', 'Could not find the specified $config[\'composer_autoload\'] path: ' . $composer_autoload); } } /** * 加载Benchmark,它很简单,就是计算任意两点之间程序的运行时间 * Benchmark是基准点的意思。 * 其实这个类的功能很简单,只是用来计算程序运行消耗的时间和内存而已。 * 以后经常在某个地方冒出来句$BM->mark('xxx');这个作用就是记录运行到当前位置的时候的时间点。 * 通过两个时间点,就可以计算出时间了。 */ $BM =& load_class('Benchmark', 'core'); $BM->mark('total_execution_time_start'); $BM->mark('loading_time:_base_classes_start'); /** * 取得Hooks组件。 * 这个hook也是非常非常棒的一个东西!它可以让我们很好地扩展和改造CI * 可以这样理解,一个应用从运行到结束这个期间,CI为我们保留了一些位置,在这些位置上面可以让开发人员放上所谓的 * “钩子”(其实就是一段程序啦!),在应用运行过程中,当运行到有可以放钩子的位置的时候,先检测开发人员有没有 * 实现这里的钩子,如果有就运行它。 * 有些地方甚至可以用自己写的钩子程序替代CI框架本来的程序。 */ $EXT =& load_class('Hooks', 'core'); //这里就有一个钩子啦。大概意思是:整个应用系统开始动了,这里要不要先让开发人员来一段程序? //如果你定义了pre_system这个钩子,那么,其实它就是在这个位置运行的。 $EXT->call_hook('pre_system'); //取得配置组件。 $CFG =& load_class('Config', 'core'); //如果有在index.php定义配置数组,那么就丢给配置组件CFG,以后就由CFG来保管了配置信息了。 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); //根据php扩展启用情况设置了MB_ENABLED,ICONV_ENABLED两个常量 //多字节支持也是一个重要的话题 if (extension_loaded('mbstring')) { define('MB_ENABLED', TRUE); @ini_set('mbstring.internal_encoding', $charset); mb_substitute_character('none'); } else { define('MB_ENABLED', FALSE); } if (extension_loaded('iconv')) { define('ICONV_ENABLED', TRUE); @ini_set('iconv.internal_encoding', $charset); } else { define('ICONV_ENABLED', FALSE); } if (is_php('5.6')) { 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'); //取得UTF-8组件,这里暂时不用管它。 $UNI =& load_class('Utf8', 'core'); //取得URI组件。 $URI =& load_class('URI', 'core'); //取得URI的好基友RTR //RTR的这个_set_routing();其实做了非常多的事情。在后面的解析Router.php也会解说。非常重要。 $RTR =& load_class('Router', 'core', isset($routing) ? $routing : NULL); //输出组件。这个输出组件有什么用?输出不是$this->load->view()么?其实它们两个也是好基友。 $OUT =& load_class('Output', 'core'); //下面是输出缓存的处理,这里允许我们自己写个hook来取替代CI原来Output类的缓存输出 //如果缓存命中则输出,并结束整个CI的单次生命周期。如果没有命中缓存,或没有启用缓存,那么将继续向下执行。 if ($EXT->call_hook('cache_override') === FALSE && $OUT->_display_cache($CFG, $URI) === TRUE) { exit; } //取得安全组件(安全组件暂时不详讲,因为对于CI一个运作流程来说,它不是必要的。CI的安全处理以后会作为一个新话题来探讨) //防xss攻击啊,csrf攻击啊 $SEC =& load_class('Security', 'core'); //取得安全组件的好基友INPUT组件。(主要是结合安全组件作一些输入方面的安全处理,$this->input->post()这些常用的操作都是 $IN =& load_class('Input', 'core'); //语言组件。 $LANG =& load_class('Lang', 'core'); //引入控制器父类文件。这里和其它组件的引入方式不一样,用load_class();因为我们最终用到的并不是这个父类, //而是我们自己写在application/controllers/下的某个由uri请求的控制器。 require_once BASEPATH . 'core/Controller.php'; //定义get_instance();方法,通过调用CI_Controller::get_instance()可以实现单例化, //调用此函数可方便以后直接取得当前应用控制器。 function &get_instance() { return CI_Controller::get_instance(); } //和其它组件一样,控制器父类同样可以通过前缀的方式进行扩展。 //其实如果能够进入这里,说明了上面的$RTR->_set_routing();在_validate_request()的时候一定是在请求默认控制器。 //引入自定义扩展controller if (file_exists(APPPATH . 'core/' . $CFG->config['subclass_prefix'] . 'Controller.php')) { require_once APPPATH . 'core/' . $CFG->config['subclass_prefix'] . 'Controller.php'; } //这里mark一下,说明CI的所需要的基本的类都加载完了。 $BM->mark('loading_time:_base_classes_end'); /** * 下面主要进行一些方法上的验证。 * 因为毕竟我们是通过URI直接调用控制器里面的方法的,其实这是个很危险的事情。 * 必须要保证我们原本没想过要通过URI访问的方法不能访问。 * * CI里面规定以_下划线开头的方法,一般是作为非公开的方法,即使方法定义为public。 * 其实不仅仅是CI这么做,把非公开的方法名以_开头,是很好的一种规范。 * 第二个就是父类CI_Controller里面的方法也是不允许通过URI访问的。 * 如果URI请求这样的方法,那么会作为404处理。 */ $e404 = FALSE; $class = ucfirst($RTR->class); $method = $RTR->method; //CI认为下面这几种情况认为是404,如果找不到就调用show_404()函数: //1) 请求的class不存在:! class_exists($class) //2) 请求私有方法:!$method[0] === '_' //3) 请求基类方法:method_exists('CI_Controller', $method) //4)请求的方法不存在:! in_array(strtolower($method), array_map('strtolower', get_class_methods($class)), TRUE) if (empty($class) OR !file_exists(APPPATH . 'controllers/' . $RTR->directory . $class . '.php')) { $e404 = TRUE; } else { require_once(APPPATH . 'controllers/' . $RTR->directory . $class . '.php'); if (!class_exists($class, FALSE) OR $method[0] === '_' OR method_exists('CI_Controller', $method)) { $e404 = TRUE; } elseif (method_exists($class, '_remap')) { $params = array($method, array_slice($URI->rsegments, 2)); $method = '_remap'; } elseif (!in_array(strtolower($method), array_map('strtolower', get_class_methods($class)), TRUE)) { $e404 = TRUE; } } //404处理 if ($e404) { if (!empty($RTR->routes['404_override'])) { 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; } } if (!$e404) { $class = $error_class; $method = $error_method; $URI->rsegments = array( 1 => $class, 2 => $method ); } else { show_404($RTR->directory . $class . '/' . $method); } } //这个_remap也是一个非常棒的东西!如果有定义,它会优先被调用,在这个方法里面我们可以根据$method和参数,随意 //做任何处理和加工。即使那个$method不存在。 if ($method !== '_remap') { $params = array_slice($URI->rsegments, 2); } //这里又一个钩子,这个钩子的位置往往都是在一些特殊的位置的,像这里就是发生在实例化控制器前。 $EXT->call_hook('pre_controller'); // Mark一下 $BM->mark('controller_execution_time_( ' . $class . ' / ' . $method . ' )_start'); //折腾了这么久,终于实例化我们想要的控制器了 //终于调用了!!!!!!!!!!!就在这里。 //不过,不是打击你,虽然我们请求的控制器的那个方法被调用了,但是实际上,我们想要的输出并没有完全输出来。 //这就是因为$this->load->view();并不是马上输出结果,而是把结果放到缓冲区,然后最后Output类把它冲出来。 $CI = new $class(); //钩子,不想多说了 $EXT->call_hook('post_controller_constructor'); //现在,所有的请求都会被定位到改控制器的index()中去了。如果_remap不存在,则调用实际控制器的$method方法 //call_user_func_array 调用回调函数,并把一个数组参数作为回调函数的参数,call_user_func_array 函数和 call_user_func 很相似 //只是使用了数组的传递参数形式,让参数的结构更清晰。 call_user_func_array(array(&$CI, $method), $params); // Mark一下 $BM->mark('controller_execution_time_( ' . $class . ' / ' . $method . ' )_end'); $EXT->call_hook('post_controller'); if ($EXT->call_hook('display_override') === FALSE) { //这里,把$this->load->view();里面缓冲的输出结果输出,基本上一个流程总算完成了。 $OUT->_display(); } //调用post_system的hook $EXT->call_hook('post_system');