CI框架提供了鉤子的方法來修改框架的內(nèi)部運(yùn)作流程夯缺,而無需修改核心文件。CodeIgniter 的運(yùn)行遵循著一個(gè)特定的流程踊兜,但通過Hook特性你可能希望在 執(zhí)行流程中的某些階段添加一些動作,可以簡單理解為主流程執(zhí)行過程中預(yù)留了交給開發(fā)者實(shí)現(xiàn)的API。鉤子的實(shí)現(xiàn)在文件Hooks.php
文件中毁葱,類名為CI_Hooks贰剥。
用戶配置
啟用鉤子
鉤子特性需要在配置文件中進(jìn)行設(shè)置,啟用鉤子特性需要在application/config/config.php
文件中設(shè)置參數(shù)為:
$config['enable_hooks'] = TRUE
掛鉤點(diǎn)列表
在上一篇文章啟動器CodeIgniter.php中鸠澈,我們介紹了CI框架的執(zhí)行過程,應(yīng)該已經(jīng)了解了CI框架中定義的掛鉤點(diǎn)列表际度。下面我們進(jìn)一步解釋每個(gè)掛鉤點(diǎn)的作用涵妥。
- pre_system 在系統(tǒng)執(zhí)行的早期調(diào)用,這個(gè)時(shí)候只有基準(zhǔn)測試類和鉤子類被加載了蓬网,還沒有執(zhí)行到路由或其他的流程。
- pre_controller 在你的控制器調(diào)用之前執(zhí)行帆锋,所有的基礎(chǔ)類都已加載,路由和安全檢查也已經(jīng)完成皮官。
- post_controller_constructor 在你的控制器實(shí)例化之后立即執(zhí)行实辑,控制器的任何方法都還尚未調(diào)用。
- post_controller 在你的控制器完全運(yùn)行結(jié)束時(shí)執(zhí)行剪撬。
- display_override 覆蓋 _display() 方法,該方法用于在系統(tǒng)執(zhí)行結(jié)束時(shí)向?yàn)g覽器發(fā)送最終的頁面結(jié)果馍佑。 這可以讓你有自己的顯示頁面的方法萍摊。注意你可能需要使用
$this->CI =& get_instance()
方法來獲取CI超級對象如叼,以及使用$this->CI->output->get_output()
方法來 獲取最終的顯示數(shù)據(jù)冰木。 - cache_override 使用你自己的方法來替代 輸出類 中的
_display_cache()
方法踊沸,這讓你有自己的緩存顯示機(jī)制。 - post_system 在最終的頁面發(fā)送到瀏覽器之后评凝、在系統(tǒng)的最后期被調(diào)用腺律。
定義鉤子特性
定義鉤子特性需要在application/config/hooks.php
文件中定義
設(shè)置的格式如:
$hook['pre_controller'] = array(
'class' => 'ClassName',
'function' => 'FunctionName',
'filename' => 'FileName',
'filepath' => 'FilePath',
'params' => 'Array('argv1','argv2')'
);
如果需要在同一個(gè)掛鉤點(diǎn)添加多個(gè)腳本,則需要將鉤子數(shù)組定義為二維數(shù)組即可
至此匀钧,我們已經(jīng)清楚了鉤子的開啟和配置方法,下面我們開始分析CI_hooks類的代碼日杈。
屬性概覽
屬性名稱 | 注釋 |
---|---|
public $enabled = FALSE | 鉤子特性是否開啟的標(biāo)志 |
public $hooks = array() | 獲取并保存定義的鉤子特性 |
protected $_objects = array() | 保存執(zhí)行過的鉤子類的實(shí)例 |
protected $_in_progess = FALSE | 確定鉤子程序的正常執(zhí)行防止死循環(huán) |
方法概覽
方法名稱 | 注釋 |
---|---|
__construct() | 鉤子類的構(gòu)造函數(shù) |
call_hook($which='') | 調(diào)用制定名稱的鉤子類 |
_run_hook($data) | 鉤子的實(shí)際執(zhí)行方法莉擒,被call_hook方法調(diào)用 |
構(gòu)造函數(shù)__construct
構(gòu)造函數(shù)主要是根據(jù)用戶配置確定是否開啟鉤子特性瘫絮,并在配置文件中尋找用戶配置的鉤子特性,如果沒有定義任何鉤子則返回麦萤,否則將更新屬性$hooks的值和將鉤子啟動標(biāo)志位$enabled置為True。
public function __construct()
{
$CFG =& load_class('Config', 'core');
log_message('info', 'Hooks Class Initialized');
//如果用戶配置中沒有開啟鉤子特性栓辜,直接返回
if ($CFG->item('enable_hooks') === FALSE)
{
return;
}
//尋找并獲取用戶配置文件中定義的鉤子特性$hook
if (file_exists(APPPATH.'config/hooks.php'))
{
include(APPPATH.'config/hooks.php');
}
if (file_exists(APPPATH.'config/'.ENVIRONMENT.'/hooks.php'))
{
include(APPPATH.'config/'.ENVIRONMENT.'/hooks.php');
}
//如果用戶沒有定義任何鉤子特性垛孔,直接返回
if ( ! isset($hook) OR ! is_array($hook))
{
return;
}
//設(shè)置類屬性$hooks和$enabled的值
$this->hooks =& $hook;
$this->enabled = TRUE;
}
調(diào)用鉤子函數(shù)call_hook()
call_hook方法就是在CI主流程中調(diào)用鉤子的方法,主要判斷是否在一個(gè)掛鉤點(diǎn)定義了多個(gè)鉤子狭莱,然后分別處理即可,實(shí)際鉤子的執(zhí)行方法是_run_hook方法
public function call_hook($which = '')
{
//沒有開啟鉤子特性或鉤子未定義直接返回FALSE
if ( ! $this->enabled OR ! isset($this->hooks[$which]))
{
return FALSE;
}
//判斷是否在這個(gè)掛鉤點(diǎn)定義了多個(gè)鉤子腋妙,并分別進(jìn)行處理
if (is_array($this->hooks[$which]) && ! isset($this->hooks[$which]['function']))
{
foreach ($this->hooks[$which] as $val)
{
$this->_run_hook($val);
}
}
else
{
$this->_run_hook($this->hooks[$which]);
}
return TRUE;
}
運(yùn)行鉤子函數(shù)_run_hook()
實(shí)際運(yùn)行鉤子函數(shù)里比較關(guān)鍵的是利用in_progess屬性保證鉤子的執(zhí)行遇到循環(huán)調(diào)用或錯誤調(diào)用時(shí)能夠跳出函數(shù)骤素,并且對定義類和方法以及只定義方法的情況都嘗試進(jìn)行調(diào)用匙睹,定義類和方法時(shí)會將類保存在屬性_objects[]數(shù)組中痕檬。
protected function _run_hook($data)
{
//判斷鉤子是否可調(diào)用送浊,如無誤根據(jù)參數(shù)位置直接執(zhí)行Object->Function
if (is_callable($data))
{
is_array($data)
? $data[0]->{$data[1]}()
: $data();
return TRUE;
}
elseif ( ! is_array($data))
{
return FALSE;
}
// -----------------------------------
// 安全處理:防止鉤子循環(huán)調(diào)用或錯誤調(diào)用
// -----------------------------------
// 如果鉤子內(nèi)部調(diào)用的自己那就形成循環(huán)調(diào)用,其他錯誤調(diào)用也需要跳出
if ($this->_in_progress === TRUE)
{
return;
}
// -----------------------------------
// 設(shè)置文件的路徑
// -----------------------------------
if ( ! isset($data['filepath'], $data['filename']))
{
return FALSE;
}
$filepath = APPPATH.$data['filepath'].'/'.$data['filename'];
//文件不存在的話直接返回False
if ( ! file_exists($filepath))
{
return FALSE;
}
// 取得類和方法的名稱和參數(shù)列表
$class = empty($data['class']) ? FALSE : $data['class'];
$function = empty($data['function']) ? FALSE : $data['function'];
$params = isset($data['params']) ? $data['params'] : '';
if (empty($function))
{
return FALSE;
}
$this->_in_progress = TRUE;
// 調(diào)用請求的類和方法
if ($class !== FALSE)
{
// 如果$class在屬性_objects[]中保存直接調(diào)用方法
if (isset($this->_objects[$class]))
{
if (method_exists($this->_objects[$class], $function))
{
$this->_objects[$class]->$function($params);
}
else
{
return $this->_in_progress = FALSE;
}
}
else
{
//如果加載類失敗直接返回置in_progress為False
class_exists($class, FALSE) OR require_once($filepath);
if ( ! class_exists($class, FALSE) OR ! method_exists($class, $function))
{
return $this->_in_progress = FALSE;
}
// 將類的實(shí)例保存在_objects[]中然后執(zhí)行方法
$this->_objects[$class] = new $class();
$this->_objects[$class]->$function($params);
}
}
else
{
//如果只在文件中定義了方法也同樣嘗試調(diào)用
function_exists($function) OR require_once($filepath);
if ( ! function_exists($function))
{
return $this->_in_progress = FALSE;
}
$function($params);
}
//執(zhí)行到此處表示未成環(huán)袭景,調(diào)用成功置in_progess為False
$this->_in_progress = FALSE;
return TRUE;
}