1、本文使用原始碼版本為PHP-7.1.19
2、本文安裝的PHP版本為7.1.19
3、使用電腦為Mac,作業系統資訊如下
Darwin Kernel Version 18.0.0: root:xnu-4903.201.2~1/RELEASE_X86_64 x86_64
4、本文提到的擴充套件開發是PHP擴充套件,而不是Zend擴充套件
5、文章原文來自於部落格,關注獲取最新文章
1、什麼是PHP擴充套件
在/php-src/Zend/zend_modules.h
標頭檔案中定義了_zend_module_entry
結構體,這個結構體就是PHP擴充套件的結構體,稱為module
,除了一些基本資訊外,主要提供了以下個鉤子函式
MINT
:模組初始化時被呼叫MSHUTDOWN
:模組結束化時被呼叫RINT
:每一次請求時被呼叫RSHUTDOWN
:每一次請求結束後被呼叫struct _zend_module_entry { // 基本資訊一般通過STANDARD_MODULE_HEADER常量填充即可 unsigned short size; unsigned int zend_api; const struct _zend_ini_entry *ini_entry; int module_started; int module_number; // 擴充套件的名稱 const char *name; // 擴充套件的函式指標, 用於獲取擴充套件的函式列表 const struct _zend_function_entry *functions; // MINT鉤子函式 int (*module_startup_func)(INIT_FUNC_ARGS); // MSHUTDOWN鉤子函式 int (*module_shutdown_func)(SHUTDOWN_FUNC_ARGS); // RINT鉤子函式 int (*request_startup_func)(INIT_FUNC_ARGS); // RSHUTDOWN鉤子函式 int (*request_shutdown_func)(SHUTDOWN_FUNC_ARGS); // 呼叫phpinfo()時列印擴充套件資訊 void (*info_func)(ZEND_MODULE_INFO_FUNC_ARGS); };
2、什麼是Zend擴充套件
在
/php-src/Zend/zend_extensions.h
標頭檔案中定義了_zend_extension
結構體,這個結構體就是Zend擴充套件的結構體,稱為extension
。相比PHP擴充套件,主要提供了更底層的鉤子函式,如下所示struct _zend_extension { // 一些基本資訊 char *name; char *version; char *author; char *URL; char *copyright; /*Zend生命週期內的一些鉤子函式*/ startup_func_t startup; shutdown_func_t shutdown; activate_func_t activate; deactivate_func_t deactivate; message_handler_func_t message_handler; op_array_handler_func_t op_array_handler; statement_handler_func_t statement_handler; fcall_begin_handler_func_t fcall_begin_handler; fcall_end_handler_func_t fcall_end_handler; op_array_ctor_func_t op_array_ctor; op_array_dtor_func_t op_array_dtor; /*Zend生命週期內的一些鉤子函式*/ };
3、舉例
(1) Json擴充套件
Json擴充套件定義結構體為
zend_module_entry
,可知它是PHP擴充套件
(2) Opcache擴充套件
Opcache擴充套件定義結構體為zend_extension
,可知它是Zend擴充套件
(3) Xdebug擴充套件
Xdebug擴充套件必須在Zend生命週期內Hook才能實現對程式碼的除錯,所以Xdebug是Zend擴充套件
4、總結
擴充套件是區分php擴充套件和zend擴充套件的,在PHP原始碼中也嚴格區分module
和extension
這兩個定義
module
表示php extension
,即PHP的擴充套件,通過extension=*載入extension
表示zend extension
,即Zend的擴充套件,通過zend_extension=*載入
1、目錄結構
在原始碼中的php-src/ext
目錄就是擴充套件目錄,如json
、mysqli
、pdo
等常用的擴充套件,其中每個擴充套件都主要由以下檔案組成
tests
:單元測試目錄config.m4
:擴充套件的編譯配置檔案(Unix系統)config.w32
:擴充套件的編譯配置檔案(Windows系統)php_{$module}.h
:擴充套件的標頭檔案{$module}.c
:擴充套件原始檔{$module}.php
:擴充套件的測試檔案
2、程式碼結構
(1) 單元測試
在編譯擴充套件成功後,會在擴充套件目錄下生成一個run-test.php
指令碼檔案,這個指令碼會自動執行tests目錄下的所有單元測試。
此外在擴充套件目錄下還會自動生成一個{$module}.php
擴充套件的測試檔案,可以方便的測試擴充套件是否可以正常載入和使用
(2) 編譯配置檔案
擴充套件下載後只有原始碼,需要進行編譯生成.so擴充套件檔案
後才可以被PHP使用,config.m4
和config.w32
這兩個檔案就是在後續執行phpize
階段會使用到的編譯配置檔案。
m4
是一種巨集處理檔案,主要由PHP_ARG_WITH
和PHP_ARG_ENABLE
兩部分構成,一般使用第二部分即可,用於開啟指定的擴充套件。這樣在編譯階段,就會判斷$PHP_PHP_HELLO
變數不是no
,從而執行此擴充套件的編譯。
其中dnl巨集
會刪除本行多餘的字元,可以簡單的理解為註釋,如下所示,如果需要編譯php_hello
這個擴充套件,把PHP_ARG_ENABLE
部分最前面的dnl巨集
都去掉即可
dnl If your extension references something external, use with:
dnl PHP_ARG_WITH(php_hello, for php_hello support,
dnl Make sure that the comment is aligned:
dnl [ --with-php_hello Include php_hello support])
dnl Otherwise use enable:
dnl PHP_ARG_ENABLE(php_hello, whether to enable php_hello support,
dnl Make sure that the comment is aligned:
dnl [ --enable-php_hello Enable php_hello support])
if test "$PHP_PHP_HELLO" != "no"; then
PHP_NEW_EXTENSION(php_hello, php_hello.c, $ext_shared,, -DZEND_ENABLE_STATIC_TSRMLS_CACHE=1)
fi
(3) 擴充套件標頭檔案
一般名為php_{$module}.h
的是擴充套件標頭檔案,一般用於定義需要的常量和函式等
(4) 擴充套件原始檔
一般名稱為{$module}.c
的是擴充套件原始檔,主要由以下部分組成
zend_module_entry
:定義擴充套件的結構體PHP_FUNCTION
:定義擴充套件的函式Hook_FUNCTION
:如PHP_MINIT_FUNCTION
等
(1) 原始碼註釋
在php-src/main/main.c
檔案中,php_module_startup()
函式會執行擴充套件的載入與初始化。
int php_module_startup(sapi_module_struct *sf, zend_module_entry *additional_modules, uint num_additional_modules)
{
// Zend引擎初始化
zend_startup(&zuf, NULL);
// 註冊常量
php_output_register_constants();
php_rfc1867_register_constants();
// 註冊ini配置
if (php_init_config() == FAILURE) {
return FAILURE;
}
REGISTER_INI_ENTRIES();
zend_register_standard_ini_entries();
// 註冊$_GET/$_POST/$_SERVER/$_REQUEST等全域性變數
php_startup_auto_globals()
// 載入靜態編譯的擴充套件這些擴充套件包含在main/internal_functions.c中
if (php_register_internal_extensions_func(TSRMLS_C) == FAILURE) {
php_printf("Unable to start builtin modules\n");
return FAILURE;
}
// 註冊SAPI的擴充套件模組,即additional_modules中的擴充套件
php_register_extensions_bc(additional_modules, num_additional_modules TSRMLS_CC);
// 根據ini配置,先載入Zend擴充套件, 再載入PHP擴充套件
php_ini_register_extensions(TSRMLS_C);
// 擴充套件初始化, 觸發MINT()鉤子
zend_startup_modules(TSRMLS_C);
zend_startup_extensions();
}
(2) php_ini_register_extensions函式
在此函式中,extension_lists
是一個儲存著解析ini配置後獲得的所有擴充套件(包括PHP擴充套件和Zend擴充套件)的連結串列,通過使用&extension_lists.engine
和&extension_lists.functions
獲取PHP擴充套件列表和Zend擴充套件連結串列,然後通過php_load_zend_extension_cb()
或php_load_php_extension_cb()
分別完成不同型別擴充套件的載入
void php_ini_register_extensions(void)
{
//註冊zend擴充套件
zend_llist_apply(&extension_lists.engine, php_load_zend_extension_cb);
//註冊php擴充套件
zend_llist_apply(&extension_lists.functions, php_load_php_extension_cb);
zend_llist_destroy(&extension_lists.engine);
zend_llist_destroy(&extension_lists.functions);
}
(3) 擴充套件的生命週期
如在PHP擴充套件與Zend擴充套件一節中看到的,這兩種擴充套件分別提供了不同的鉤子函式,這些函式在PHP生命週期內的呼叫順序如下圖所示
1、獲取PHP原始碼
獲取PHP原始碼後,切換至7.1.19
版本,按如下命令操作
git clone https://github.com/php/php-src
git checkout remotes/origin/PHP-7.1.19
2、生成擴充套件的基礎檔案
切換到ext
擴充套件目錄下,在此目錄下,有一個ext_skel
指令碼,可以用來自動生成擴充套件的基礎檔案。比如建立一個print_hello
的擴充套件,按如下命令操作
cd php-src/ext/
./ext_skel --extname=print_hello
執行成功後,會得到如下提示
回到在ext
目錄下,發現已經成功生成print_hello
目錄,主要包含如下檔案
tests
:單元測試目錄config.m4
:擴充套件的編譯配置檔案(Unix系統)config.w32
:擴充套件的編譯配置檔案(Windows系統)php_print_hello.h
:擴充套件的標頭檔案print_hello.c
:擴充套件原始檔print_hello.php
:擴充套件的測試檔案
3、修改編譯配置檔案
開啟config.m4
配置檔案,如下所示
找到PHP_ARG_ENABLE
這段程式碼,刪掉前面的dnl
巨集,如下所示
# 修改前
dnl PHP_ARG_ENABLE(print_hello, whether to enable print_hello support,
dnl Make sure that the comment is aligned:
dnl [ --enable-print_hello Enable print_hello support])
# 修改後
PHP_ARG_ENABLE(print_hello, whether to enable print_hello support,
Make sure that the comment is aligned:
[ --enable-print_hello Enable print_hello support])
4、修改print_hello.c檔案
找到PHP_FUNCTION
(表示定義的擴充套件函式),在如下confirm_print_hello_compiled
函式中新增一句輸出hello world
的程式碼
PHP_FUNCTION(confirm_print_hello_compiled)
{
char *arg = NULL;
size_t arg_len, len;
zend_string *strg;
if (zend_parse_parameters(ZEND_NUM_ARGS(), "s", &arg, &arg_len) == FAILURE) {
return;
}
strg = strpprintf(0, "Congratulations! You have successfully modified ext/%.78s/config.m4. Module %.78s is now compiled into PHP.", "print_hello", arg);
// 新增下面輸出hello world的程式碼
php_printf("hello world!\n");
RETURN_STR(strg);
}
5、編譯擴充套件
通過執行以下命令執行對擴充套件的編譯處理
cd print_hello
phpize
./configure --with-php-config=/usr/bin/php-config
make
sudo make
執行make
命令成功後如下所示
執行sudo make install
命令成功後如下所示
6、執行擴充套件測試指令碼
測試指令碼會先動態載入print_hello
擴充套件,並輸出擴充套件中所有提供的函式,最後執行在print_hello.c
原始檔中定義的confirm_print_hello_compiled
函式,如果正常執行則說明擴充套件載入且執行成功
$br = (php_sapi_name() == "cli")? "":"<br>";
// 判斷擴充套件是否已載入
if(!extension_loaded('print_hello')) {
// 在執行時動態載入擴充套件庫
// 如果載入失敗,需要修改php.ini配置檔案,直接開啟動態載入擴充套件的選項enable_dl = On即可,在命令列下執行不需要重啟PHP
dl('print_hello.' . PHP_SHLIB_SUFFIX);
}
$module = 'print_hello';
// 依次輸出擴充套件提供的所有函式
$functions = get_extension_funcs($module);
echo "Functions available in the test extension:$br\n";
foreach($functions as $func) {
echo $func."$br\n";
}
echo "$br\n";
// 如果擴充套件載入成功, 則執行 confirm_print_hello_compiled 函式
$function = 'confirm_' . $module . '_compiled';
if (extension_loaded($module)) {
$str = $function($module);
} else {
$str = "Module $module is not compiled into PHP";
}
echo "$str\n";
指令碼執行成功後如下所示
7、結束
到目前為止,簡單的print_hello
擴充套件就已經開發完成,當然還可以在print_hello.c
原始檔中定義更多的擴充套件函式,做更多有趣的事情!不過篇幅有限就不再講解,關於擴充套件的高階使用請關注部落格,獲取最新文章!
本作品採用《CC 協議》,轉載必須註明作者和本文連結