在開發擴充之前,最好瞭解下PHP核心的執行流程,PHP大概包括三個方面:
SAPI
Zend VM
內部擴充
Zend VM是PHP的虛擬機器,與JVM類似,都是各自語言的編譯/執行的核心。它們都會把各自的代碼先編譯為一種中間代碼,PHP的通常叫opcode,Java通常叫bytecode,不同的是PHP的opcode直接被Zend VM的執行單元調用對應的C函數執行,不會顯示保留下來(可以cache保留),而Java通常是產生class檔案保留下來。而這一點可能也是PHP interpreter的名稱的由來吧。其實相對嚴格的C/C++等編譯型語言,PHP和Java更多的是結合了編譯型和解釋性的風格。
SAPI可以看作是Zend VM向外界提供編譯/執行PHP代碼服務的方式和規範。無論是作為cli/cgi/fastcgi/apache_mod與其他程式互動,還是嵌入其他語言中如C/C++等,都可以通過SAPI的規範實現。它的一個重要資料結構就是sapi_module_struct(main/SAPI.h line 217)
內部擴充部分可以看作是搭建在Zend VM和SAPI之上的庫,為PHP開發人員提供效能和易用性上的保證。Java的各種包/Python的各種模組功能類似,不同的是PHP中為了效能是用C擴充來實現的,類似的在Java中可以通過JNI來實現,Python中如_socket和_select(多工)都不是原生Python實現。
生命週期
關於各種SAPI或者PHP本身的生命週期,可能會和其他組件如apache耦合,後續再細談。關於PHP擴充的生命週期,這裡借用一張圖。流程應該是很容易明白的,關於這個過程,網上也有很多資料,不再贅述。我們開發擴充需要注意的幾個地方也可以對應到圖中的某些節點:
全域變數的定義,通常是zend_modulename_globals
模組的初始化,包括資源/類/常量/ini配置等模組層級的初始化
請求的初始化,包括與單次請求相關的一些初始化
請求的結束,清理單次請求相關的資料/記憶體
模組的卸載,清理模組相關的資料/記憶體
基本上我們要做的就是按照上面的流程,實現相關的內建函數,定義自己的資源/全域變數/類/函數等。值得注意的地方是在在嵌入其他語言如Python或者被嵌入其他組件如apache時,要小心多進程多線程相關的問題。
PHP擴充結構
使用php-src/ext/ext_skel可以產生PHP擴充的架構
./ext_skel --extname=myext[tan@tan ~/software/needbak/php-5.5.20/ext 12:24]$==> ls myext/config.m4 config.w32 CREDITS EXPERIMENTAL myext.c myext.php php_myext.h tests
比較重要的檔案是config.m4(當然還有源碼),config.m4檔案可以使用phpize命令產生configure檔案,其中說明了我們是否開啟模組,以及外部依賴的庫。
//config.m4//如果你的擴充依賴其他外部庫dnl PHP_ARG_WITH(myext, for myext support,dnl Make sure that the comment is aligned:dnl [ --with-myext Include myext support])//擴充不依賴外部庫dnl PHP_ARG_ENABLE(myext, whether to enable myext support,dnl Make sure that the comment is aligned:dnl [ --enable-myext Enable myext support])//尋找並包含標頭檔if test "$PHP_MYEXT" != "no"; then dnl Write more examples of tests here... dnl # --with-myext -> check with-path dnl SEARCH_PATH="/usr/local /usr" # you might want to change this dnl SEARCH_FOR="/include/myext.h" # you most likely want to change this dnl if test -r $PHP_MYEXT/$SEARCH_FOR; then # path given as parameter dnl MYEXT_DIR=$PHP_MYEXT dnl else # search default path list dnl AC_MSG_CHECKING([for myext files in default path]) dnl for i in $SEARCH_PATH ; do dnl if test -r $i/$SEARCH_FOR; then dnl MYEXT_DIR=$i dnl AC_MSG_RESULT(found in $i) dnl fi dnl done dnl fi dnl dnl if test -z "$MYEXT_DIR"; then dnl AC_MSG_RESULT([not found]) dnl AC_MSG_ERROR([Please reinstall the myext distribution]) dnl fi dnl # --with-myext -> add include path dnl PHP_ADD_INCLUDE($MYEXT_DIR/include) //載入的lib位置 dnl # --with-myext -> check for lib and symbol presence dnl LIBNAME=myext # you may want to change this dnl LIBSYMBOL=myext # you most likely want to change this dnl PHP_CHECK_LIBRARY($LIBNAME,$LIBSYMBOL, dnl [ dnl PHP_ADD_LIBRARY_WITH_PATH($LIBNAME, $MYEXT_DIR/$PHP_LIBDIR, MYEXT_SHARED_LIBADD) dnl AC_DEFINE(HAVE_MYEXTLIB,1,[ ]) dnl ],[ dnl AC_MSG_ERROR([wrong myext lib version or lib not found]) dnl ],[ dnl -L$MYEXT_DIR/$PHP_LIBDIR -lm dnl ]) dnl dnl PHP_SUBST(MYEXT_SHARED_LIBADD) PHP_NEW_EXTENSION(myext, myext.c, $ext_shared)fi
//php_myext.h#ifndef PHP_MYEXT_H#define PHP_MYEXT_Hextern zend_module_entry myext_module_entry;#define phpext_myext_ptr &myext_module_entry//匯出符號,在連結的時候有用#ifdef PHP_WIN32# define PHP_MYEXT_API __declspec(dllexport)#elif defined(__GNUC__) && __GNUC__ >= 4# define PHP_MYEXT_API __attribute__ ((visibility("default")))#else# define PHP_MYEXT_API#endif#ifdef ZTS#include "TSRM.h"#endif//幾個核心函數的聲明PHP_MINIT_FUNCTION(myext);PHP_MSHUTDOWN_FUNCTION(myext);PHP_RINIT_FUNCTION(myext);PHP_RSHUTDOWN_FUNCTION(myext);PHP_MINFO_FUNCTION(myext);//自動產生的測試函式宣告,我們自己定義的模組函數需要在此聲明PHP_FUNCTION(confirm_myext_compiled); //全域變數在這定義,展開後是zend_myext_globals結構體ZEND_BEGIN_MODULE_GLOBALS(myext) long global_value; char *global_string;ZEND_END_MODULE_GLOBALS(myext)//安全執行緒與非安全執行緒下擷取全域變數的方式#ifdef ZTS#define MYEXT_G(v) TSRMG(myext_globals_id, zend_myext_globals *, v)#else#define MYEXT_G(v) (myext_globals.v)#endif#endif /* PHP_MYEXT_H */
//myext.c#ifdef HAVE_CONFIG_H#include "config.h"#endif#include "php.h"#include "php_ini.h"#include "ext/standard/info.h"#include "php_myext.h"//全域變數聲明ZEND_DECLARE_MODULE_GLOBALS(myext)/* True global resources - no need for thread safety here */static int le_myext;//模組函數的匯出const zend_function_entry myext_functions[] = { PHP_FE(confirm_myext_compiled, NULL) /* For testing, remove later. */ PHP_FE_END /* Must be the last line in myext_functions[] */};//模組結構zend_module_entry myext_module_entry = {#if ZEND_MODULE_API_NO >= 20010901 STANDARD_MODULE_HEADER,#endif "myext", myext_functions, PHP_MINIT(myext), PHP_MSHUTDOWN(myext), PHP_RINIT(myext), /* Replace with NULL if there's nothing to do at request start */ PHP_RSHUTDOWN(myext), /* Replace with NULL if there's nothing to do at request end */ PHP_MINFO(myext),#if ZEND_MODULE_API_NO >= 20010901 PHP_MYEXT_VERSION,#endif STANDARD_MODULE_PROPERTIES};#ifdef COMPILE_DL_MYEXTZEND_GET_MODULE(myext)#endif//ini設定檔的設定PHP_INI_BEGIN() STD_PHP_INI_ENTRY("myext.global_value", "42", PHP_INI_ALL, OnUpdateLong, global_value, zend_myext_globals, myext_globals) STD_PHP_INI_ENTRY("myext.global_string", "foobar", PHP_INI_ALL, OnUpdateString, global_string, zend_myext_globals, myext_globals)PHP_INI_END()//初始化全域變數static void php_myext_init_globals(zend_myext_globals *myext_globals){ myext_globals->global_value = 0; myext_globals->global_string = NULL;}//模組載入時的函數PHP_MINIT_FUNCTION(myext){ /* If you have INI entries, uncomment these lines REGISTER_INI_ENTRIES(); */ return SUCCESS;}//模組卸載時函數PHP_MSHUTDOWN_FUNCTION(myext){ /* uncomment this line if you have INI entries UNREGISTER_INI_ENTRIES(); */ return SUCCESS;}//請求初始化函數PHP_RINIT_FUNCTION(myext){ return SUCCESS;}//請求關閉函數PHP_RSHUTDOWN_FUNCTION(myext){ return SUCCESS;}//模組資訊,phpinfoPHP_MINFO_FUNCTION(myext){ php_info_print_table_start(); php_info_print_table_header(2, "myext support", "enabled"); php_info_print_table_end(); /* Remove comments if you have entries in php.ini DISPLAY_INI_ENTRIES(); */}//測試函數PHP_FUNCTION(confirm_myext_compiled){ char *arg = NULL; int arg_len, len; char *strg; if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s", &arg, &arg_len) == FAILURE) { return; } len = spprintf(&strg, 0, "Congratulations! You have successfully modified ext/%.78s/config.m4. Module %.78s is now compiled into PHP.", "myext", arg); RETURN_STRINGL(strg, len, 0);}