這篇文章主要介紹了PHP擴展開發入門教程,本文講解了使用C語言在Linux系統下開發一個PHP擴展應該具備的最基本知識,需要的朋友可以參考下
PHP擴展開發
我准備在此系列博文中總結我有關PHP擴展開發的學習和感悟,力圖簡單清晰地描述在Linux系統下開發一個PHP擴展應該具備的最基本知識。水平較低,難免有錯誤,望指出。
准備工作
首先要獲取一份PHP源碼(可以從Github上簽出,或者到官網上下載最新的穩定版),然後編譯之。為了加快編譯速度,我們推薦禁用所有額外的擴展(使用--disable-all選項),但最好打開debug(使用--enable-debug選項)和線程安全(使用--enable-maintainer-zts),但要在發布擴展的時候關閉debug,視情況選擇是否需要打開線程安全:
代碼如下:
編譯後的PHP的可執行程序在源碼的sapi目錄下,對應不同的宿主環境有不同的子目錄,我們以後都主要使用cli(command line interface)環境,可以建一個別名方便引用:
代碼如下:
有一些命令行選項是很有用的:
代碼如下:
擴展骨架
PHP的所有官方擴展都在源碼的ext目錄下,我們自己寫的擴展也可以放在該目錄下。注意,該目錄下有個名為ext_skel的shell腳本,它是用來生成PHP擴展骨架的,使用該腳本,可以幫我們快速創建PHP擴展:
代碼如下:
接下來讓我們完成我們的擴展。進入myext目錄,編輯config.m4配置文件,找到PHP_ARG_ENABLE宏函數,去掉前面的dnl注釋(共三行)。退回到源碼根目錄,重新執行buildconf、configure和make命令:
代碼如下:
注意,我們用./configure --help | grep myext打印了我們擴展的加載情況,如果看不到下面的輸出,則說明我們的擴展沒有配置成功,回頭檢查下config.m4文件。
這次編譯應該非常快,因為大部分代碼都已經編譯過了。PHP還有另外一種編譯擴展的方法(使用動態連接的方式,將擴展編譯為.so的文件),不過我們推薦在開發擴展的時候使用靜態編譯,因為這樣省去了在配置文件中加載擴展的步驟。
一切順利的話,我們的第一個擴展就已經可以執行了:
代碼如下:
手動創建擴展
大部分教程都是以ext_skel擴展骨架為原型講述擴展開發的,這種做法當然很方便快捷。但是我個人更喜歡純手工開發擴展的方式,因為這樣更容易理解其中的每一個細節。
手動創建擴展,先進入ext目錄,創建我們的擴展目錄myext2。有幾個文件是必須的:config.m4,myext2.c和php_myext2.h。
首先,我們來編寫配置文件config.m4:
代碼如下:
if test "PHP_MYEXT2" != "no"; then
PHP_NEW_EXTENSION(myext2, myext2.c, $ext_shared)
fi
PHP_ARG_ENABLE是PHP為autoconf定義的宏函數,myext2是它的第一個參數,指出了擴展的名字;後面兩個參數只是在make和configure執行時用來顯示的,所以我們可以隨便寫。[ ]在autoconf語法中的作用類似於雙引號,用來包裹字符串(注意第二個參數中包含了空格,但是可以不用方括號起來)。還有第四個參數用來指明擴展默認是開啟還是關閉(yes或no),默認是no。
下面三行其實就是shell語法,判斷我們是否開啟了PHP_MYEXT2擴展模塊。如果開啟了該擴展模塊(--enable-myext2),則$PHP_MYEXT2變量的值不為no,因此執行PHP_NEW_EXTENSION宏。這個宏函數也是PHP為autoconf定義的擴展語法,第一個參數同樣是擴展名稱;第二個參數是擴展要編譯的C文件,如果有多個,依次寫下去就可以了(空格分隔);第三個參數固定是$ext_shared。
接下來編寫php_myext2.h頭文件,該文件的命名是PHP擴展的規范 — php_擴展名.h:
代碼如下:
extern zend_module_entry myext2_module_entry;
#define phpext_myext2_ptr &myext2_module_entry
#define PHP_MYEXT2_VERSION "0.1.0"
/* prototypes */
PHP_FUNCTION(hello);
#endif /* PHP_MYEXT2_H */
這裡主要的代碼是定義了名為phpext_myext2_ptr的宏,PHP底層通過該宏來引用我們的擴展。可以看出,該宏的命名同樣是有規范的 — phpext_擴展名_ptr。而myext2_module_entry是我們稍後要在.c文件裡定義的結構體,它的命名也是規范的 — 擴展名_module_entry。
此外我們還定義了一個標識我們擴展版本號的宏和一個函數原型(通過PHP_FUNCTION宏,PHP_FUNCTION宏函數的參數是外部可使用的函數名),稍後我們會來實現這個函數。
最後來看下myext2.c文件的實現:
代碼如下:
/* {{{ myext2_functions[]
*
* Every user visible function must have an entry in myext2_functions[].
*/
static const zend_function_entry myext2_functions[] = {
PHP_FE(hello, NULL)
PHP_FE_END
};
/* }}} */
/* {{{ myext2_module_entry
*/
zend_module_entry myext2_module_entry = {
STANDARD_MODULE_HEADER,
"myext2", /* module name */
myext2_functions, /* module functions */
NULL, /* module initialize */
NULL, /* module shutdown */
NULL, /* request initialize */
NULL, /* request shutdown */
NULL, /* phpinfo */
PHP_MYEXT2_VERSION, /* module version */
STANDARD_MODULE_PROPERTIES
};
/* }}} */
#ifdef COMPILE_DL_MYEXT2
ZEND_GET_MODULE(myext2)
#endif
/* {{{ proto void hello()
Print "hello world!" */
PHP_FUNCTION(hello)
{
php_printf("hello world!n");
}
/* }}} */
對比下擴展骨架創建的.c文件就會發現,我們的.c文件非常的簡單,其實這些對一個最基本的擴展來說就已經足夠了。
上面的代碼是簡單而清晰的,大部分注釋已經很具說明性了。我們再簡要概括下:
1.開頭包含我們要用到的頭文件。php.h是必須的,它已經幫我們包含了我們會用到的絕大多數的標准庫文件,比如stdio.h,stdlib.h等等。
2.myext2_functions定義了由我們要暴露出去的函數構成的結構體數組,每一個元素通過PHP_FE宏來指定。PHP_FE宏有兩個參數,第一個是外部可使用的函數名,第二個是參數信息(這裡我們簡單使用了NULL),最後一個元素必須是PHP_FE_END。注意它的注釋,再次強調,每一個要暴露給外部使用的函數,都必須在該結構體數組中有定義。
3.myext2_module_entry定義了我們的模塊信息,它是一個結構體,大部分屬性都已經通過注釋給出了說明。注意中間的五個函數指針,我們都簡單的置為了NULL,在後續的博文中會講述它們的用法。
4.ZEND_GET_MODULE(myext2)宏函數是被ifdef宏包含的,所以說它是否調用是視情況而定的。至於什麼情況下會被調用,什麼情況下不會被調用,在後續的博文中會講述。
5.最後幾行代碼我們實現了hello函數,它很簡單,調用php_printf輸出hello world!跟一個換行符,php_printf的用法和printf完全一樣。
6.注釋裡的 {{{ 和 }}} 是為了方便vim等編輯器折疊而使用的,我們推薦你也這樣來寫注釋。
這裡面涉及了一些宏,比如PHP_FE,PHP_FE_END,PHP_FUNCTION等等,完整介紹這些宏要到後續的博文中才可以,眼下最簡單的辦法就是記住這些宏。
注意到我們每一個文件的命名,變量的命名,空格和縮進,以及注釋等都是非常規范的,遵循這些規范,可以使我們編寫的代碼和PHP本身的代碼更加契合,我們也推薦你使用這樣的規范來開發PHP擴展。
最後,編譯運行我們的擴展:
代碼如下:
$ php-dev -m | grep myext2
myext2
$ php-dev -r 'hello();'
hello world!