CI框架允許你在不修改系統核心代碼的基礎上添加或者更改系統的核心功能(如重寫緩存、輸出等)。例如,在系統開啟hook的條件下(config.php中$config['enable_hooks'] = TRUE;
),通過添加特定的鉤子,可以讓系統在特定的時刻觸發特定的腳本:
$hook['post_system'] = array( 'class' => 'frameLog', 'function' => 'postLog', 'filename' => 'post_system.php', 'filepath' => 'hooks', );
上述鉤子定義了一個post_system的鉤子,用於在最終的頁面渲染之後的腳本處理(參數的含義可以參考後面或者手冊,這裡暫時不做更多解釋)。
那麼問題來了:
我們一步步來看。
百度百科上對於鉤子的定義是:
鉤子實際上是一個處理消息的程序段,通過系統調用,把它掛入系統。每當特定的消息發出,在沒有到達目的窗口前,鉤子程序就先捕獲該消息,亦即鉤子函數先得到控制權。這時鉤子函數即可以加工處理(改變)該消息,也可以不作處理而繼續傳遞該消息,還可以強制結束消息的傳遞。
從上述定義我們可以看出幾點:
CI中提供了7個可用的預設掛鉤點,分別是:
pre_system: 指在系統加載前期的鉤子
pre_controller:調用控制器之前的鉤子,路由與安全性檢查已經完畢
post_controller_constructor:控制器實例化之後,任何方法調用之前
post_controller:控制器完全運行之後
display_override:重寫display
cache_override :重寫緩存
post_system:最終的頁面發送到客戶端之後
CI中鉤子的核心功能是由Hook組件完成的,先看該組件的類圖:
其中:
enabled: 鉤子功能是否開啟的標志。
hooks :保存系統中啟用的鉤子列表
in_progress:之後我們會看到,這個標志位用於防止鉤子之間的互相調用而導致的死循環。
_construct是Hook組件的構造函數,這其中調用了_initialize來完成初始化的工作
_call_hook: 調用_run_hook來call指定的鉤子程序。之前CodeIgniter.php中我們已經看到,_call_hook是實際提供給外部調用的接口。
_run_hook: 實際執行鉤子程序的函數
在開始之前,我們先貼出預定義鉤子的結構。這個結構可能會貫穿在源代碼的始終,因而我們有必要知道該結構的參數含義。
$hook['xx'] = array( 'class' => 'xx', //鉤子調用的類名,可以為空 'function' => 'xx',//鉤子調用的函數名 'filename' => 'xx',//該鉤子的文件名 'filepath' => 'xx',//鉤子的目錄 'params' => 'xx'//傳遞給鉤子的參數 );
1. 鉤子組件初始化
_initialize函數用於鉤子組件的初始化,該函數主要完成的工作有:
(1) 檢查配置文件中hook功能是否被啟用,這需要加載Config(配置管理組件):
$CFG =& load_class('Config', 'core'); if ($CFG->item('enable_hooks') == FALSE) { return; }
(2) 加載定義的hook列表
同樣,你可以設定不同的ENVIRONMENT啟用不同的hook,如果有的話,優先加載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的檢查。如果未設置任何hook,或者設置的hook格式錯誤,則不作任何處理,直接退出:
if ( ! isset($hook) OR ! is_array($hook)) { return; }
經過initialize之後,Hook::hooks中存儲了已經定義的hook列表:
$this->hooks =& $hook;
2. Call指定的鉤子
_call_hook是主程序中直接調用的接口。該接口主要的工作有:
(1). 檢查鉤子是否被啟用,以及call的鉤子是否被預定義(如果未啟用或者call的鉤子不存在,則直接返回):
if ( ! $this->enabled OR ! isset($this->hooks[$which])) { return FALSE; }
(2). 檢查同一個掛鉤點是否啟用了多個鉤子,如果有,則依次執行之:
if (isset($this->hooks[$which][0]) AND is_array($this->hooks[$which][0])) { foreach ($this->hooks[$which] as $val) { $this->_run_hook($val); } }
(3). 否則,只有一個鉤子,執行它
else { $this->_run_hook($this->hooks[$which]); }
_run_hook是實際執行hook的函數。
3. run執行特定的鉤子程序
_run_hook函數是hook的實際執行者,該函數接收一個預定義的hook數組作為參數,實現如下:
(1). 如果傳遞的參數壓根就不是數組(自然也就不是有效的hook),那麼直接返回:
if ( ! is_array($data)) { return FALSE; }
(2). 檢查hook執行狀態。
in_progress用於標志當前hook的執行狀態。這個參數的主要作用,是防止hook之間的相互調用而導致的死循環。
if ($this->in_progress == TRUE) { return; }
(3). Hook的合法性檢查。
為了方便講述,我們再次提出一個預定義的hook需要的參數:
$hook['xx'] = array( 'class' => 'xx', //鉤子調用的類名,可以為空 'function' => 'xx',//鉤子調用的函數名 'filename' => 'xx',//該鉤子的文件名 'filepath' => 'xx',//鉤子的目錄 'params' => 'xx'//傳遞給鉤子的參數 );
其中class和params是可選參數,其他3個參數為必選參數,如果不提供,則由於無法准確定位到hook程序,只能直接返回:
if ( ! isset($data['filepath']) OR ! isset($data['filename'])) { return FALSE; } $filepath = APPPATH.$data['filepath'].'/'.$data['filename']; if ( ! file_exists($filepath)) { return FALSE; }
(4). 到這裡,已經基本確認鉤子程序的位置了,這裡有兩種情況:
a. 預定義的hook中class參數為空,表明使用的是過程式的調用方式,則直接執行hook文件中的function xxx
b. class參數不為空,提供的是面向對象的方式,則實際的鉤子程序是$class->$function .同樣,如果既沒有設置class,也沒有設置function參數,則無法執行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 參數 */ if (isset($data['params'])) { $params = $data['params']; } /* 如果class和function都不存在,則無法定位hook程序,直接返回 */ if ($class === FALSE AND $function === FALSE) { return FALSE; }
(5). 設置執行標志in_progress,並執行上述兩種情況下的hook:
/* 面向對象的設置方式 */ if ($class !== FALSE) { if ( ! class_exists($class)) { require($filepath); } $HOOK = new $class; $HOOK->$function($params); } /* 過程式的執行方式 */ else { if ( ! function_exists($function)) { require($filepath); } $function($params); }
最後,別忘了在hook執行完之後,設置標識位in_progress為false,並返回執行成功的標志:
$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; } }
1. http://codeigniter.org.cn/user_guide/general/hooks.html 手冊
2. http://itopic.org/codeigniter-hook.html
3. http://codeigniter.org.cn/forums/thread-4947-1-1.html 鉤子實現的Layout
這個……好像CI沒啥方法,倒是可以通過寫模板的時候include進去header.php和footer.php,倒是還有聽說smarty模板引擎中有模板繼承這個概念,可以讓你的內容頁繼承某個頁面,那個頁面上寫著header.php和footer.php,貌似CI是可以使用smarty模板引擎的,不過我沒有那樣用過,還有,CI有hook(鉤子)這個東西,老實說我沒用過,不知道它能不能實現。
是記錄有多少人進去你的網站還是說點擊的某個連接多少次?