CI框架允許你在不修改系統(tǒng)核心代碼的基礎(chǔ)上添加或者更改系統(tǒng)的核心功能(如重寫(xiě)緩存、輸出等)。例如,在系統(tǒng)開(kāi)啟hook的條件下(config.php中$config['enable_hooks'] = TRUE;
),通過(guò)添加特定的鉤子,可以讓系統(tǒng)在特定的時(shí)刻觸發(fā)特定的腳本:
$hook['post_system'] = array( 'html' target='_blank'>class' => 'frameLog', 'function' => 'postLog', 'filename' => 'post_system.php', 'filepath' => 'hooks',);
上述鉤子定義了一個(gè)post_system的鉤子,用于在最終的頁(yè)面渲染之后的腳本處理(參數(shù)的含義可以參考后面或者手冊(cè),這里暫時(shí)不做更多解釋?zhuān)?/p>
那么問(wèn)題來(lái)了:
鉤子是什么? CI中支持的鉤子有哪些? CI中鉤子是如何實(shí)現(xiàn)的?我們一步步來(lái)看。
1. 鉤子是什么百度百科上對(duì)于鉤子的定義是:
鉤子實(shí)際上是一個(gè)處理消息的程序段,通過(guò)系統(tǒng)調(diào)用,把它掛入系統(tǒng)。每當(dāng)特定的消息發(fā)出,在沒(méi)有到達(dá)目的窗口前,鉤子程序就先捕獲該消息,亦即鉤子函數(shù)先得到控制權(quán)。這時(shí)鉤子函數(shù)即可以加工處理(改變)該消息,也可以不作處理而繼續(xù)傳遞該消息,還可以強(qiáng)制結(jié)束消息的傳遞。
從上述定義我們可以看出幾點(diǎn):
鉤子是一種事件驅(qū)動(dòng)模式,它的核心自然是事件(CI中pre_system,pre_controller等都是特定的事件)。 既然是事件驅(qū)動(dòng),那么必然要包含最重要的兩個(gè)步驟: (1)、事件注冊(cè)。對(duì)于Hook而言,就是指Hook鉤子的掛載。(2).事件觸發(fā)。在特定的時(shí)間點(diǎn)call特定的鉤子,執(zhí)行相應(yīng)的鉤子程序。 既然是事件驅(qū)動(dòng),那么也應(yīng)該支持統(tǒng)一掛鉤點(diǎn)的多個(gè)注冊(cè)事件。 啟動(dòng)Hook鉤子之后,程序的流程可能會(huì)發(fā)生變化,且鉤子之間可能有相互調(diào)用的可能性,如果處理不當(dāng),會(huì)有死循環(huán)的可能性。同時(shí),鉤子的啟用使得程序在一定程度上變得復(fù)雜,難以調(diào)試。2. CI中預(yù)定義鉤子CI中提供了7個(gè)可用的預(yù)設(shè)掛鉤點(diǎn),分別是:
pre_system: 指在系統(tǒng)加載前期的鉤子
pre_controller:調(diào)用控制器之前的鉤子,路由與安全性檢查已經(jīng)完畢
post_controller_constructor:控制器實(shí)例化之后,任何方法調(diào)用之前
post_controller:控制器完全運(yùn)行之后
display_override:重寫(xiě)display
cache_override :重寫(xiě)緩存
post_system:最終的頁(yè)面發(fā)送到客戶(hù)端之后
3. CI中鉤子的實(shí)現(xiàn)CI中鉤子的核心功能是由Hook組件完成的,先看該組件的類(lèi)圖:
其中:
enabled: 鉤子功能是否開(kāi)啟的標(biāo)志。
hooks :保存系統(tǒng)中啟用的鉤子列表
in_progress:之后我們會(huì)看到,這個(gè)標(biāo)志位用于防止鉤子之間的互相調(diào)用而導(dǎo)致的死循環(huán)。
_construct是Hook組件的構(gòu)造函數(shù),這其中調(diào)用了_initialize來(lái)完成初始化的工作
_call_hook: 調(diào)用_run_hook來(lái)call指定的鉤子程序。之前CodeIgniter.php中我們已經(jīng)看到,_call_hook是實(shí)際提供給外部調(diào)用的接口。
_run_hook: 實(shí)際執(zhí)行鉤子程序的函數(shù)
在開(kāi)始之前,我們先貼出預(yù)定義鉤子的結(jié)構(gòu)。這個(gè)結(jié)構(gòu)可能會(huì)貫穿在源代碼的始終,因而我們有必要知道該結(jié)構(gòu)的參數(shù)含義。
$hook['xx'] = array( 'class' => 'xx', //鉤子調(diào)用的類(lèi)名,可以為空 'function' => 'xx',//鉤子調(diào)用的函數(shù)名 'filename' => 'xx',//該鉤子的文件名 'filepath' => 'xx',//鉤子的目錄 'params' => 'xx'//傳遞給鉤子的參數(shù));
1. 鉤子組件初始化
_initialize函數(shù)用于鉤子組件的初始化,該函數(shù)主要完成的工作有:
(1) 檢查配置文件中hook功能是否被啟用,這需要加載Config(配置管理組件):
$CFG =& load_class('Config', 'core');if ($CFG->item('enable_hooks') == FALSE){ return;}
(2) 加載定義的hook列表
同樣,你可以設(shè)定不同的ENVIRONMENT啟用不同的hook,如果有的話(huà),優(yōu)先加載ENVRIONMENT下的hook:
if (defined('ENVIRONMENT') AND is_file(APPPATH.'config/'.ENVIRONMENT.'/hooks.php')){ include(APPPATH.'config/'.ENVIRONMENT.'/hooks.php');}elseif (is_file(APPPATH.'config/hooks.php')){ include(APPPATH.'config/hooks.php');}
(3) Hook的檢查。如果未設(shè)置任何hook,或者設(shè)置的hook格式錯(cuò)誤,則不作任何處理,直接退出:
if ( ! isset($hook) OR ! is_array($hook)){ return;}
經(jīng)過(guò)initialize之后,Hook::hooks中存儲(chǔ)了已經(jīng)定義的hook列表:
$this->hooks =& $hook;
2. Call指定的鉤子
_call_hook是主程序中直接調(diào)用的接口。該接口主要的工作有:
(1). 檢查鉤子是否被啟用,以及call的鉤子是否被預(yù)定義(如果未啟用或者call的鉤子不存在,則直接返回):
if ( ! $this->enabled OR ! isset($this->hooks[$which])){ return FALSE;}
(2). 檢查同一個(gè)掛鉤點(diǎn)是否啟用了多個(gè)鉤子,如果有,則依次執(zhí)行之:
if (isset($this->hooks[$which][0]) AND is_array($this->hooks[$which][0])){ foreach ($this->hooks[$which] as $val) { $this->_run_hook($val); }}
(3). 否則,只有一個(gè)鉤子,執(zhí)行它
else{ $this->_run_hook($this->hooks[$which]);}
_run_hook是實(shí)際執(zhí)行hook的函數(shù)。
3. run執(zhí)行特定的鉤子程序
_run_hook函數(shù)是hook的實(shí)際執(zhí)行者,該函數(shù)接收一個(gè)預(yù)定義的hook數(shù)組作為參數(shù),實(shí)現(xiàn)如下:
(1). 如果傳遞的參數(shù)壓根就不是數(shù)組(自然也就不是有效的hook),那么直接返回:
if ( ! is_array($data)){ return FALSE;}
(2). 檢查hook執(zhí)行狀態(tài)。
in_progress用于標(biāo)志當(dāng)前hook的執(zhí)行狀態(tài)。這個(gè)參數(shù)的主要作用,是防止hook之間的相互調(diào)用而導(dǎo)致的死循環(huán)。
if ($this->in_progress == TRUE){ return;}
(3). Hook的合法性檢查。
為了方便講述,我們?cè)俅翁岢鲆粋€(gè)預(yù)定義的hook需要的參數(shù):
$hook['xx'] = array( 'class' => 'xx', //鉤子調(diào)用的類(lèi)名,可以為空 'function' => 'xx',//鉤子調(diào)用的函數(shù)名 'filename' => 'xx',//該鉤子的文件名 'filepath' => 'xx',//鉤子的目錄 'params' => 'xx'//傳遞給鉤子的參數(shù));
其中class和params是可選參數(shù),其他3個(gè)參數(shù)為必選參數(shù),如果不提供,則由于無(wú)法準(zhǔn)確定位到hook程序,只能直接返回:
if ( ! isset($data['filepath']) OR ! isset($data['filename'])){ return FALSE;}$filepath = APPPATH.$data['filepath'].'/'.$data['filename'];if ( ! file_exists($filepath)){ return FALSE;}
(4). 到這里,已經(jīng)基本確認(rèn)鉤子程序的位置了,這里有兩種情況:
a. 預(yù)定義的hook中class參數(shù)為空,表明使用的是過(guò)程式的調(diào)用方式,則直接執(zhí)行hook文件中的function xxx
b. class參數(shù)不為空,提供的是面向?qū)ο?/u>的方式,則實(shí)際的鉤子程序是$class->$function .同樣,如果既沒(méi)有設(shè)置class,也沒(méi)有設(shè)置function參數(shù),則無(wú)法執(zhí)行hook,直接返回:
$class = FALSE;$function = FALSE;$params = '';/* 獲取 hook class */if (isset($data['class']) AND $data['class'] != ''){ $class = $data['class'];}/* 獲取 hook function */if (isset($data['function'])){ $function = $data['function'];}/* 獲取傳遞的 hook 參數(shù) */if (isset($data['params'])){ $params = $data['params'];}/* 如果class和function都不存在,則無(wú)法定位hook程序,直接返回 */if ($class === FALSE AND $function === FALSE){ return FALSE;}
(5). 設(shè)置執(zhí)行標(biāo)志in_progress,并執(zhí)行上述兩種情況下的hook:
/* 面向?qū)ο蟮脑O(shè)置方式 */if ($class !== FALSE){ if ( ! class_exists($class)) { require($filepath); } $HOOK = new $class; $HOOK->$function($params);}/* 過(guò)程式的執(zhí)行方式 */else{ if ( ! function_exists($function)) { require($filepath); } $function($params);}
最后,別忘了在hook執(zhí)行完之后,設(shè)置標(biāo)識(shí)位in_progress為false,并返回執(zhí)行成功的標(biāo)志:
$this->in_progress = FALSE;return TRUE;
Hook組件的完整源碼:
<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');class CI_Hooks { /** * Determines wether hooks are enabled * * @var bool */ var $enabled = FALSE; /** * List of all hooks set in config/hooks.php * */ var $hooks = array(); /** * Determines wether hook is in progress, used to prevent infinte loops * */ var $in_progress = FALSE; /** * Constructor */ function __construct() { $this->_initialize(); log_message('debug', 'Hooks Class Initialized'); } /** * Initialize the Hooks Preferences * * @access private * @return void */ function _initialize() { $CFG =& load_class('Config', 'core'); // If hooks are not enabled in the config file // there is nothing else to do if ($CFG->item('enable_hooks') == FALSE) { return; } if (defined('ENVIRONMENT') AND is_file(APPPATH.'config/'.ENVIRONMENT.'/hooks.php')) { include(APPPATH.'config/'.ENVIRONMENT.'/hooks.php'); } elseif (is_file(APPPATH.'config/hooks.php')) { include(APPPATH.'config/hooks.php'); } if ( ! isset($hook) OR ! is_array($hook)) { return; } $this->hooks =& $hook; $this->enabled = TRUE; } /** * Call Hook * * Calls a particular hook * * @access private * @param string the hook name * @return mixed */ function _call_hook($which = '') { if ( ! $this->enabled OR ! isset($this->hooks[$which])) { return FALSE; } if (isset($this->hooks[$which][0]) AND is_array($this->hooks[$which][0])) { foreach ($this->hooks[$which] as $val) { $this->_run_hook($val); } } else { $this->_run_hook($this->hooks[$which]); } return TRUE; } /** * Run Hook * * Runs a particular hook * * @access private * @param array the hook details * @return bool */ function _run_hook($data) { if ( ! is_array($data)) { return FALSE; } // If the script being called happens to have the same hook call within it a loop can happen if ($this->in_progress == TRUE) { return; } if ( ! isset($data['filepath']) OR ! isset($data['filename'])) { return FALSE; } $filepath = APPPATH.$data['filepath'].'/'.$data['filename']; if ( ! file_exists($filepath)) { return FALSE; } $class = FALSE; $function = FALSE; $params = ''; if (isset($data['class']) AND $data['class'] != '') { $class = $data['class']; } if (isset($data['function'])) { $function = $data['function']; } if (isset($data['params'])) { $params = $data['params']; } if ($class === FALSE AND $function === FALSE) { return FALSE; } $this->in_progress = TRUE; // Call the requested class and/or function if ($class !== FALSE) { if ( ! class_exists($class)) { require($filepath); } $HOOK = new $class; $HOOK->$function($params); } else { if ( ! function_exists($function)) { require($filepath); } $function($params); } $this->in_progress = FALSE; return TRUE; }}PHP編程
鄭重聲明:本文版權(quán)歸原作者所有,轉(zhuǎn)載文章僅為傳播更多信息之目的,如作者信息標(biāo)記有誤,請(qǐng)第一時(shí)間聯(lián)系我們修改或刪除,多謝。
新聞熱點(diǎn)
疑難解答
圖片精選
網(wǎng)友關(guān)注