CI框架源碼閱讀筆記4 引導文件CodeIgniter.php,
到了這裡,終於進入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
EXT
CFG
UNI
URI
RTR
OUT
SEC
其中:
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的核心執行流程:

回顧之前我們引用的官方給出的流程圖,是不是基本一致的:

最後,貼上整個文件的源碼:

![]()
<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');
/**
* CodeIgniter Version
*/
define('CI_VERSION', '2.1.4');
/**
* CodeIgniter Branch (Core = TRUE, Reactor = FALSE)
*/
define('CI_CORE', FALSE);
require(BASEPATH.'core/Common.php');
/* 如有有相應的環境的配置文件,優先加載配置下的常量定義文件,否則加載config目錄下的常量定義文件 */
if (defined('ENVIRONMENT') AND file_exists(APPPATH.'config/'.ENVIRONMENT.'/constants.php'))
{
require(APPPATH.'config/'.ENVIRONMENT.'/constants.php');
}
else
{
require(APPPATH.'config/constants.php');
}
set_error_handler('_exception_handler');
if ( ! is_php('5.3'))
{
@set_magic_quotes_runtime(0); // Kill magic quotes
}
/*
* Set the subclass_prefix
*/
if (isset($assign_to_config['subclass_prefix']) AND $assign_to_config['subclass_prefix'] != '')
{
get_config(array('subclass_prefix' => $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 */
View Code
由於寫作倉促,難免會有錯誤。歡迎隨時指出,歡迎溝通交流。
參考文獻: