Hook (鉤子) 是 CodeIgniter 提供的核心擴充機制,允許在框架執行關鍵節點 (預載入、控制器前/後、輸出前等) 注入自訂邏輯,而不修改核心檔案。本文深入剖析 system/core/Hooks.php 源碼、鉤子點時序、定義格式與實戰應用模式。
class CI_Hooks {
protected $enabled = FALSE;
protected $hooks = []; // 來自 config/hooks.php
protected $in_progress = FALSE;
public function __construct() {
$this->_initialize();
}
protected function _initialize() {
// 1. 讀取 config.php $config['enable_hooks']
// 2. include(APPPATH . 'config/hooks.php') → $hook
// 3. $this->hooks = $hook; $this->enabled = TRUE;
}
public function _call_hook($which) {
if (!$this->enabled || empty($this->hooks[$which])) return;
$hooks = $this->hooks[$which];
// 支援單一陣列或多個陣列
is_array(reset($hooks)) ? $this->_run_multiple($hooks) : $this->_run_hook($hooks);
}
protected function _run_hook($hook) {
// $hook = ['class'=>'', 'function'=>'', 'filename'=>'', 'filepath'=>'', 'params'=>[]]
$filepath = APPPATH . $hook['filepath'] . '/' . $hook['filename'];
include_once $filepath;
$obj = new $hook['class']();
$obj->{$hook['function']}($hook['params']);
}
}
1. pre_system → 系統極早期,Benchmark/Config/Uri 尚未載入
2. pre_controller → 路由完成、控制器類載入前
3. post_controller_constructor → 控制器 __construct() 執行後、方法執行前
4. post_controller → 控制器方法執行完畢
5. display_override → 覆寫 _display(),完全接管輸出
6. cache_override → 覆寫 _display_cache(),自訂快取機制
7. post_system → 最終輸出送出瀏覽器前
$hook['pre_controller'] = [
'class' => 'AuthHook',
'function' => 'check_login',
'filename' => 'AuthHook.php',
'filepath' => 'hooks',
'params' => ['redirect' => 'login']
];
// 同一掛載點多個 Hook (依陣列順序執行)
$hook['post_controller'] = [
['class' => 'LogHook', 'function' => 'write_access_log', 'filename' => 'LogHook.php', 'filepath' => 'hooks'],
['class' => 'ProfilerHook', 'function' => 'inject_profiler', 'filename' => 'ProfilerHook.php', 'filepath' => 'hooks'],
];
// application/hooks/AuthHook.php
class AuthHook {
public function check_login($params = []) {
$CI =& get_instance();
$public = ['login', 'register', 'forgot_password'];
$controller = $CI->router->fetch_class();
$method = $CI->router->fetch_method();
if (in_array($controller, $public) || in_array($method, $public)) return;
if (!$CI->session->userdata('user_id')) {
$redirect = $params['redirect'] ?? 'login';
redirect($redirect);
}
}
}
class LanguageHook {
public function detect() {
$CI =& get_instance();
$lang = $CI->input->cookie('lang') ?? substr($_SERVER['HTTP_ACCEPT_LANGUAGE'], 0, 2);
$CI->config->set_item('language', in_array($lang, ['zh-tw','en']) ? $lang : 'zh-tw');
}
}
class ApiResponseHook {
public function format() {
$CI =& get_instance();
if (strpos($CI->router->fetch_class(), 'Api_') === 0) {
$data = $CI->output->get_output();
$json = json_decode($data, TRUE);
$CI->output->set_content_type('application/json')
->set_output(json_encode([
'code' => 0,
'msg' => 'success',
'data' => $json ?? $data
]));
}
}
}
class CustomCacheHook {
public function serve() {
$CI =& get_instance();
// 依使用者角色、Header 做差異化快取
$cache_key = 'page_' . $CI->uri->uri_string() . '_' . $CI->session->userdata('role');
if ($cached = $CI->cache->get($cache_key)) {
$CI->output->set_output($cached);
return TRUE; // 告知框架已處理
}
return FALSE; // 繼續正常流程
}
}
config.php → hooks.php → Hooks.php 初始化;修改 hooks.php 即時生效,無需重啟。exit() 會跳過後續流程。$CI =& get_instance(); 而非 $this->ci (Hook 類非 CI 控制器)。require_once 或自動載入。$CI 注入依賴。| 特性 | CI3 Hook | CI3 Library | CI4 Filter |
|---|---|---|---|
| 執行時機 | 固定 7 個點 | 手動載入/自動載入 | Before/After 路由 |
| 耦合度 | 低 (配置驅動) | 中 (需在 Controller 呼叫) | 低 (屬性標註) |
| 適用場景 | 橫切關注點 (日誌、權限、快取) | 業務邏輯封裝 | 認證、CSRF、速率限制 |