為解決效能瓶頸或封裝 C 函式庫,開發 PHP 擴展是進階工程師必備技能。本文以實作 util_str_repeat(string, int) 為例,完整記錄 Ubuntu 環境下從產生骨架到安裝驗證的每一步。
# Ubuntu 20.04+ sudo apt update sudo apt install -y php-dev build-essential autoconf automake libtool pkg-config # php-dev 提供 phpize、php-config、headers
# 下載對應版本原始碼(以 8.2 為例) wget https://www.php.net/distributions/php-8.2.20.tar.gz tar xzf php-8.2.20.tar.gz cd php-8.2.20/ext # 建立函式定義檔 util.def echo 'string util_str_repeat(string str, long n)' > util.def # 產生骨架 ./ext_skel --extname=util --proto=util.def cd util
# config.m4 PHP_ARG_ENABLE(util, whether to enable util support, [ --enable-util Enable util support])
// util.c 片段
PHP_FUNCTION(util_str_repeat) {
char *str = NULL;
size_t str_len;
zend_long n;
ZEND_PARSE_PARAMETERS_START(2, 2)
Z_PARAM_STRING(str, str_len)
Z_PARAM_LONG(n)
ZEND_PARSE_PARAMETERS_END();
if (n <= 0) {
RETURN_EMPTY_STRING();
}
// 防止整數溢位
if (str_len > 0 && n > (ZEND_SIZE_MAX / str_len)) {
zend_error(E_ERROR, "Resulting string too large");
return;
}
size_t result_len = str_len * n;
char *result = emalloc(result_len + 1);
char *ptr = result;
while (n--) {
memcpy(ptr, str, str_len);
ptr += str_len;
}
*ptr = '\0';
RETVAL_STRINGL(result, result_len);
efree(result); // RETVAL_STRINGL 會複製,需手動釋放
}
cd /path/to/php-8.2.20/ext/util phpize ./configure --enable-util make -j$(nproc) sudo make install # 輸出:Installing shared extensions: /usr/lib/php/20220829/util.so
# 建立 ini 檔(PHP 8.2 模組目錄) echo "extension=util.so" | sudo tee /etc/php/8.2/mods-available/util.ini sudo phpenmod util sudo systemctl restart php8.2-fpm # 或 apache2
php -m | grep util
# 輸出: util
php -r 'for ($i=1;$i<=3;$i++) echo util_str_repeat("Test ", $i) . "\n";'
# 輸出:
# Test
# Test Test
# Test Test Test
gdb --args php test.php 進入 C 層除錯。valgrind --tool=memcheck php test.php 偵測記憶體洩漏。zend.assertions=1、assert.exception=1 捕獲斷言。| 主題 | 關鍵點 |
|---|---|
| 類別與物件 | zend_class_entry, object_properties_init(), zend_object_std_init() |
| 資源型別 | zend_register_list_destructors_ex(), ZEND_REGISTER_RESOURCE() |
| INI 設定 | PHP_INI_BEGIN(), STD_PHP_INI_ENTRY, OnUpdateLong |
| 生命週期鉤子 | PHP_MINIT_FUNCTION, PHP_RINIT_FUNCTION, PHP_MSHUTDOWN_FUNCTION |
| 測試 | .phpt 檔案,make test 執行 |