CI框架原始碼閱讀筆記6 擴充套件鉤子 Hook.php

FreeeLinux發表於2017-09-05

CI框架允許你在不修改系統核心程式碼的基礎上新增或者更改系統的核心功能(如重寫快取、輸出等)。例如,在系統開啟hook的條件下(config.php中config[enablehooks]=TRUE;

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(配置管理元件):

[php] view plain copy print?
$CFG =& load_class(‘Config’, ‘core’);

if ($CFG->item(‘enable_hooks’) == FALSE)
{
return;
}
(2) 載入定義的hook列表

同樣,你可以設定不同的ENVIRONMENT啟用不同的hook,如果有的話,優先載入ENVRIONMENT下的hook:

[php] view plain copy print?
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格式錯誤,則不作任何處理,直接退出:

[php] view plain copy print?
if ( ! isset(hook)OR!isarray(

hook) OR ! is_array(
hook))
{
return;
}

經過initialize之後,Hook::hooks中儲存了已經定義的hook列表:

[php] view plain copy print?
this->hooks =&

this->hooks =&
hook;
2.  Call指定的鉤子

_call_hook是主程式中直接呼叫的介面。該介面主要的工作有:

(1). 檢查鉤子是否被啟用,以及call的鉤子是否被預定義(如果未啟用或者call的鉤子不存在,則直接返回):

[php] view plain copy print?
if ( ! this>enabledOR!isset(

this->enabled OR ! isset(
this->hooks[$which]))
{
return FALSE;
}
(2). 檢查同一個掛鉤點是否啟用了多個鉤子,如果有,則依次執行之:

[php] view plain copy print?
if (isset(this>hooks[

this->hooks[
which][0]) AND is_array(this>hooks[
this->hooks[
which][0]))
{
foreach (this>hooks[
this->hooks[
which] as val)  
    {

}
}
(3). 否則,只有一個鉤子,執行它

[php] view plain copy print?
else
{
this>runhook(
}
_run_hook是實際執行hook的函式。

3.  run執行特定的鉤子程式

_run_hook函式是hook的實際執行者,該函式接收一個預定義的hook陣列作為引數,實現如下:

(1). 如果傳遞的引數壓根就不是陣列(自然也就不是有效的hook),那麼直接返回:

[php] view plain copy print?
if ( ! is_array($data))
{
return FALSE;
}
(2). 檢查hook執行狀態。

in_progress用於標誌當前hook的執行狀態。這個引數的主要作用,是防止hook之間的相互呼叫而導致的死迴圈。

[php] view plain copy print?
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程式,只能直接返回:

[php] view plain copy print?
if ( ! isset(data[filepath])OR!isset(

data['filepath']) OR ! isset(
data[‘filename’]))
{
return FALSE;
}

filepath=APPPATH.

filepath = APPPATH.
data[‘filepath’].’/’.$data[‘filename’];

if ( ! file_exists($filepath))
{
return FALSE;
}
(4). 到這裡,已經基本確認鉤子程式的位置了,這裡有兩種情況:

a. 預定義的hook中class引數為空,表明使用的是過程式的呼叫方式,則直接執行hook檔案中的function xxx

b. class引數不為空,提供的是物件導向的方式,則實際的鉤子程式是class>

class->
function .同樣,如果既沒有設定class,也沒有設定function引數,則無法執行hook,直接返回:

[php] view plain copy print?
class=FALSE;

class = FALSE;
function = FALSE;
$params = ”;

/* 獲取 hook class */
if (isset(data[class])AND

data['class']) AND
data[‘class’] != ”)
{
class=
class =
data[‘class’];
}

/* 獲取 hook function */
if (isset(data[‘function’]))  
{

data[‘function’])) {
function = $data[‘function’];
}

/* 獲取傳遞的 hook 引數 */
if (isset(data[‘params’]))  
{

data[‘params’])) {
params = $data[‘params’];
}

/* 如果class和function都不存在,則無法定位hook程式,直接返回 */
if (class===FALSEAND

class === FALSE AND
function === FALSE)
{
return FALSE;
}
(5). 設定執行標誌in_progress,並執行上述兩種情況下的hook:

[php] view plain copy print?
/* 物件導向的設定方式 */
if (class !== FALSE)  
{  
    if ( ! class_exists(

class !== FALSE) { if ( ! class_exists(
class))
{
require($filepath);
}

$HOOK = new $class;  
$HOOK->$function($params);  

}

else
{
if ( ! function_exists(function))  
    {  
        require(

}

$function($params);  

}
最後,別忘了在hook執行完之後,設定標識位in_progress為false,並返回執行成功的標誌:

[php] view plain copy print?
$this->in_progress = FALSE;
return TRUE;
Hook元件的完整原始碼:

[php] view plain copy print?

相關文章