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中支援的鉤子有哪些?
- CI中鉤子是如何?的?
我們一步步來看。
1. 鉤子是什麼
百度百科上對於鉤子的定義是:
鉤子實際上是一個處理訊息的程式段,通過系統調用,把它掛入系統。每當特定的訊息發出,在沒有到達目的視窗前,鉤子程式就先捕獲該訊息,亦即鉤子函數先得到控制權。這時鉤子函數即可以加工處理(改變)該訊息,也可以不作處理而繼續傳遞該訊息,還可以強制結束訊息的傳遞。
從上述定義我們可以看出幾點:
- 鉤子是一種事件驅動模式,它的核心自然是事件(CI中pre_system,pre_controller等都是特定的事件)。
- 既然是事件驅動,那麼必然要包含最重要的兩個步驟: (1)、事件註冊。對於Hook而言,就是指Hook鉤子的掛載。(2).事件觸發。在特定的時間點call特定的鉤子,執行相應的鉤子程式。
- 既然是事件驅動,那麼也應該支援統一掛鈎點的多個註冊事件。
- 啟動Hook鉤子之後,程式的流程可能會發生變化,且鉤子之間可能有相互調用的可能性,如果處理不當,會有死迴圈的可能性。同時,鉤子的啟用使得程式在一定程度上變得複雜,難以調試。
2. CI中預定義鉤子
CI中提供了7個可用的預設掛鈎點,分別是:
pre_system: 指在系統載入前期的鉤子
pre_controller:調用控制器之前的鉤子,路由與安全性檢查已經完畢
post_controller_constructor:控制器執行個體化之後,任何方法調用之前
post_controller:控制器完全運行之後
display_override:重寫display
cache_override :重寫緩衝
post_system:最終的頁面發送到用戶端之後
3. CI中鉤子的實現
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(組態管理組件):
| 1 2 3 4 5 6 |
$CFG =& load_class( 'Config' , 'core' ); if ( $CFG ->item( 'enable_hooks' ) == FALSE) { return ; } |
(2) 載入定義的hook列表
同樣,你可以設定不同的ENVIRONMENT啟用不同的hook,如果有的話,優先載入ENVRIONMENT下的hook:
| 1 2 3 4 5 6 7 8 |
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格式錯誤,則不作任何處理,直接退出:
| 1 2 3 4 |
if ( ! isset( $hook ) OR ! is_array ( $hook )) { return ; } |
經過initialize之後,Hook::hooks中儲存了已經定義的hook列表:
| 1 |
$this ->hooks =& $hook ; |
2. Call指定的鉤子
_call_hook是主程式中直接調用的介面。該介面主要的工作有:
(1). 檢查鉤子是否被啟用,以及call的鉤子是否被預定義(如果未啟用或者call的鉤子不存在,則直接返回):
| 1 2 3 4 |
if ( ! $this ->enabled OR ! isset( $this ->hooks[ $which ])) { return FALSE; } |
(2). 檢查同一個掛鈎點是否啟用了多個鉤子,如果有,則依次執行之:
| 1 2 3 4 5 6 7 |
if (isset( $this ->hooks[ $which ][0]) AND is_array ( $this ->hooks[ $which ][0])) { foreach ( $this ->hooks[ $which ] as $val ) { $this ->_run_hook( $val ); } } |
(3). 否則,只有一個鉤子,執行它
| 1 2 3 4 |
else { $this ->_run_hook( $this ->hooks[ $which ]); } |
_run_hook是實際執行hook的函數。
3. run執行特定的鉤子程式
_run_hook函數是hook的實際執行者,該函數接收一個預定義的hook數組作為參數,實現如下:
(1). 如果傳遞的參數壓根就不是數組(自然也就不是有效hook),那麼直接返回:
| 1 2 3 4 |
if ( ! is_array ( $data )) { return FALSE; } |
(2). 檢查hook執行狀態。
in_progress用於標誌當前hook的執行狀態。這個參數的主要作用,是防止hook之間的相互調用而導致的死迴圈。
| 1 2 3 4 |
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程式,只能直接返回:
| 1 2 3 4 5 6 7 8 9 10 11 |
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,直接返回:
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
$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:
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
/* 物件導向的設定方式 */ 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,並返回執行成功的標誌:
| 1 2 |
$this ->in_progress = FALSE; return TRUE; |
Hook組件的完整源碼:
+ View Code
參考文獻
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