到了這裡,終於進入CI框架的核心了。既然是“引導”文件,那麼就是對用戶的請求、參數等做相應的導向,讓用戶請求和數據流按照正確的線路各就各位。例如,用戶的請求url:
http://you.host.com/usr/reg
經過引導文件,實際上會交給Application中的UsrController控制器的reg方法去處理。 這之中,CodeIgniter.php做了哪些工作?我們一步步來看。
之前的一篇博客(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會借助一系列的組件,完成更多的需求。
通常,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)
各組件的功能和具體實現之後會有詳細的分析, 這裡我們只需要知道該組件的基本功能即可。
調用很簡單,只有一句話:
$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()等分別獲取目錄、類、和方法。
到了這一步,CI會先檢查是否有cache_override這個鉤子(默認情況下沒有配置,也就是返回FALSE),如果沒有注冊,則調用_display_cache方法輸出緩存(這種說法並不准確,准確來說應該是,如果有相應的緩存,則輸出緩存且直接退出程序,否則返回FALSE,這裡我們暫時不去思考實現細節):
if ($EXT->_call_hook('cache_override') === FALSE) { if ($OUT->_display_cache($CFG, $URI) == TRUE) { exit; } }
能夠走到這裡,說明之前的緩存是沒有命中的(實際上,任何頁面都是應該先走到這一步,然後才會有設置緩存,之後的訪問檢查緩存才會命中)。這一步會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由於寫作倉促,難免會有錯誤。歡迎隨時指出,歡迎溝通交流。