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');