PHP 擴展以 C 語言撰寫,能突破使用者空間效能瓶頸、封裝 C 庫、實現語言層面無法做的底層操作。本文以 PHP 5.6/7.x 為例,完整記錄從源碼編譯、骨架生成、手工撰寫擴展到編譯驗證的全流程,並解析核心結構體與巨集用途。
# 1. 取得 PHP 源碼(以 7.4 為例)
git clone --depth=1 --branch PHP-7.4 https://github.com/php/php-src.git
cd php-src
# 2. 產生 configure 腳本
./buildconf --force
# 3. 編譯 PHP(開發模式:禁用所有擴展、開啟 debug、啟用 ZTS)
./configure --disable-all --enable-debug --enable-maintainer-zts
make -j$(nproc)
# 4. 建立別名方便測試
alias php-dev="$(pwd)/sapi/cli/php"
# 驗證
php-dev -v
php-dev -m # 僅顯示 Core、date 等內建模組
cd ext
./ext_skel --extname=myext
cd myext
# 編輯 config.m4:取消註解 PHP_ARG_ENABLE 三行
# 返回源碼根目錄重新建置
cd ../..
./buildconf --force
./configure --disable-all --enable-myext --enable-debug --enable-maintainer-zts
make
# 驗證
php-dev -m | grep myext
php-dev -r 'echo confirm_myext_compiled("myext") . "\n";'
目錄結構:
ext/myext2/
├── config.m4
├── php_myext2.h
└── myext2.c
PHP_ARG_ENABLE(myext2, whether to enable myext2 support,
[ --enable-myext2 Enable myext2 support])
if test "$PHP_MYEXT2" != "no"; then
PHP_NEW_EXTENSION(myext2, myext2.c, $ext_shared)
fi
#ifndef PHP_MYEXT2_H
#define PHP_MYEXT2_H
extern zend_module_entry myext2_module_entry;
#define phpext_myext2_ptr &myext2_module_entry
#define PHP_MYEXT2_VERSION "0.1.0"
PHP_FUNCTION(hello); // 函式原型宣告
#endif
#include "php.h"
#include "php_myext2.h"
/* 函式表:暴露給 PHP 的函式列表 */
static const zend_function_entry myext2_functions[] = {
PHP_FE(hello, NULL) // 參數資訊暫用 NULL
PHP_FE_END
};
/* 模組進入點結構體 */
zend_module_entry myext2_module_entry = {
STANDARD_MODULE_HEADER,
"myext2", // 模組名稱
myext2_functions, // 函式表
NULL, // MINIT - 模組啟動
NULL, // MSHUTDOWN - 模組關閉
NULL, // RINIT - 請求啟動
NULL, // RSHUTDOWN - 請求關閉
NULL, // MINFO - phpinfo()
PHP_MYEXT2_VERSION, // 版本
STANDARD_MODULE_PROPERTIES
};
#ifdef COMPILE_DL_MYEXT2
ZEND_GET_MODULE(myext2) // 動態載入時需匯出此符號
#endif
/* hello 函式實作 */
PHP_FUNCTION(hello)
{
php_printf("hello world!\n");
}
| 巨集/結構 | 用途 | 關鍵點 |
|---|---|---|
zend_function_entry | 函式表元素 | 每個公開函式必須在此登記,以 PHP_FE_END 結尾 |
zend_module_entry | 模組資訊總表 | 包含名稱、函式表、五大生命週期鉤子、版本、屬性旗標 |
PHP_FUNCTION(name) | 函式簽名展開 | 實際展開為 void zif_name(INTERNAL_FUNCTION_PARAMETERS) |
PHP_FE(name, arg_info) | 函式表項目 | 第二參數為 zend_arg_info*,用於型別提示與反射 |
ZEND_GET_MODULE | 動態載入匯出符號 | 僅當 COMPILE_DL_* 定義時編入(共享庫模式) |
php_printf | 安全輸出到 PHP 輸出流 | 等同 fprintf(php_output_get_stream(), ...),支援輸出緩衝 |
cd /path/to/php-src
./buildconf --force
./configure --disable-all --enable-myext2 --enable-debug --enable-maintainer-zts
make
# 測試
php-dev -m | grep myext2
php-dev -r 'hello();' # 輸出: hello world!
zend_parse_parameters()、ZEND_PARSE_PARAMETERS_* 巨集、zend_arg_info 定型。zval 結構、引用計數、ZVAL_* 巨集、陣列/物件 API。zend_class_entry、屬性/方法註冊、zend_object 手工實作。emalloc/efree、持久化分配、GC 整合。gdb --args php-dev ...、zend_dump_debug_info()、Valgrind 檢測洩漏。PHP 擴展開發門檻較高,但掌握後能解決效能熱點、整合 C/C++ 生態、實現語言層無法達到的功能。建議從「手工撰寫最小擴展」開始,逐步深入 Zend Engine 內核機制。