CI框架源码解析二之引导文件CodeIgniter.php

         上篇解析入口文件写到载入 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');

评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值