PHP 擴展透過四對生命週期鉤子參與 SAPI 啟動/關閉與請求處理流程。本文建立 sample4 完整骨架,詳細解析各階段時機、用途、回傳值意義與實作模式。
四階段時機總覽
| 鉤子 | 觸發時機 | 執行頻率 | 典型用途 |
MINIT | SAPI 啟動後、首個請求前 | 每進程一次 | 註冊常數、類別、資源型別、INI 設定、流包裝器 |
MSHUTDOWN | SAPI 關閉前 | 每進程一次 | 釋放持久資源、關閉連線池、清理全域狀態 |
RINIT | 每個請求開始 | 每請求一次 | 初始化請求級變數、建立 DB 連線、啟動 Session |
RSHUTDOWN | 每個請求結束 | 每請求一次 | 釋放請求級資源、提交/回滾事務、寫入 Session |
sample4 完整骨架程式碼
php-src/ext/sample4/
├── config.m4
├── php_sample4.h
└── sample4.c
config.m4
PHP_ARG_ENABLE(sample4, [Whether to enable the "sample4" extension],
[ --enable-sample4 Enable "sample4" extension support])
if test "$PHP_SAMPLE4" != "no"; then
PHP_SUBST(SAMPLE4_SHARED_LIBADD)
PHP_NEW_EXTENSION(sample4, sample4.c, $ext_shared)
fi
php_sample4.h
#ifndef PHP_SAMPLE4_H
#define PHP_SAMPLE4_H
#define PHP_SAMPLE4_EXTNAME "sample4"
#define PHP_SAMPLE4_EXTVER "1.0"
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "php.h"
extern zend_module_entry sample4_module_entry;
#define phpext_sample4_ptr &sample4_module_entry
#endif
sample4.c
#include "php_sample4.h"
#include "ext/standard/info.h" // php_info_print_table_* 需要
static function_entry php_sample4_functions[] = {
{NULL, NULL, NULL}
};
// MINIT: 模組初始化
PHP_MINIT_FUNCTION(sample4) {
// 註冊常數
REGISTER_LONG_CONSTANT("SAMPLE4_VERSION", 1, CONST_CS | CONST_PERSISTENT);
// 註冊資源型別
// le_sample4_res = zend_register_list_destructors_ex(...);
// 註冊 INI 設定 (見下一章)
// REGISTER_INI_ENTRIES();
return SUCCESS;
}
// MSHUTDOWN: 模組關閉
PHP_MSHUTDOWN_FUNCTION(sample4) {
// 取消註冊 INI 設定
// UNREGISTER_INI_ENTRIES();
// 釋放持久資源
// zend_hash_destroy(&persistent_resources);
return SUCCESS;
}
// RINIT: 請求初始化
PHP_RINIT_FUNCTION(sample4) {
// 初始化請求級全域變數
// SG(my_global) = 0;
// 建立資料庫連線 (非持久)
// SG(db_link) = mysql_connect(...);
return SUCCESS;
}
// RSHUTDOWN: 請求關閉
PHP_RSHUTDOWN_FUNCTION(sample4) {
// 關閉非持久連線
// if (SG(db_link)) mysql_close(SG(db_link));
// 清理請求級資源
return SUCCESS;
}
// MINFO: phpinfo() 顯示資訊
PHP_MINFO_FUNCTION(sample4) {
php_info_print_table_start();
php_info_print_table_header(2, "sample4 support", "enabled");
php_info_print_table_row(2, "Version", PHP_SAMPLE4_EXTVER);
php_info_print_table_row(2, "Author", "Your Name");
php_info_print_table_end();
// 顯示 INI 設定
DISPLAY_INI_ENTRIES();
}
zend_module_entry sample4_module_entry = {
#if ZEND_MODULE_API_NO >= 20010901
STANDARD_MODULE_HEADER,
#endif
PHP_SAMPLE4_EXTNAME,
php_sample4_functions,
PHP_MINIT(sample4),
PHP_MSHUTDOWN(sample4),
PHP_RINIT(sample4),
PHP_RSHUTDOWN(sample4),
PHP_MINFO(sample4),
#if ZEND_MODULE_API_NO >= 20010901
PHP_SAMPLE4_EXTVER,
#endif
STANDARD_MODULE_PROPERTIES
};
#ifdef COMPILE_DL_SAMPLE4
ZEND_GET_MODULE(sample4)
#endif
關鍵規則:回傳值決定成敗
- 所有鉤子**必須回傳
SUCCESS (0)** 表示成功。
- 回傳
FAILURE (-1) 將導致:
| 階段回傳 FAILURE | 後果 |
| MINIT | 模組載入失敗,PHP 啟動中止 (CLI) 或 SAPI 拒絕載入 |
| RINIT | 請求處理中止,直接進入 RSHUTDOWN,回傳 500/白頁 |
| MSHUTDOWN/RSHUTDOWN | 記錄錯誤日誌,但不中斷流程 |
線程安全環境 (ZTS) 額外鉤子
GINIT / GSHUTDOWN:每線程啟動/關閉 (僅 ZTS)。
- 用於線程局部儲存 (TLS) 初始化與清理。
- 在
zend_module_entry 中對應 globals_ctor / globals_dtor。
實作最佳實踐
- MINIT:只做「一次性、跨請求持久」的初始化。避免耗時操作阻塞啟動。
- RINIT:輕量級請求初始化。資料庫連線建議用持久連線 (MINIT 建立) 或連線池。
- RSHUTDOWN:確保釋放**所有**請求級資源,防止洩漏累積。
- MSHUTDOWN:對稱釋放 MINIT 分配的持久資源。
- MINFO:提供有用的除錯資訊 (版本、作者、INI 設定、統計計數器)。
常見錯誤模式
// 錯誤:在 MINIT 中建立非持久資源
PHP_MINIT_FUNCTION(bad) {
SG(db) = mysql_connect(...); // 錯誤!MINIT 無請求上下文
return SUCCESS;
}
// 正確:持久連線在 MINIT,非持久在 RINIT
PHP_MINIT_FUNCTION(good) {
SG(persistent_db) = mysql_pconnect(...); // 持久連線
return SUCCESS;
}
PHP_RINIT_FUNCTION(good) {
SG(request_db) = mysql_connect(...); // 非持久,請求級
return SUCCESS;
}
PHP_RSHUTDOWN_FUNCTION(good) {
if (SG(request_db)) mysql_close(SG(request_db));
return SUCCESS;
}