PHP-X是我在2017年年初創(chuàng)建的一個(gè)新項(xiàng)目。這個(gè)項(xiàng)目的目標(biāo)就是讓有一定工作經(jīng)驗(yàn)的PHP程序都能夠具備擴(kuò)展開發(fā)的能力。
0x00 初衷
從2012年開始編寫swoole,現(xiàn)在算來(lái)已經(jīng)有5個(gè)年頭了。我發(fā)現(xiàn)編寫一個(gè)PHP 擴(kuò)展這個(gè)工作非常艱難。PHP 程序員群體中,甚至可以說(shuō) 100 人中都很難找出一個(gè)會(huì)編寫 PHP 擴(kuò)展的人來(lái)。PHP 官方對(duì)擴(kuò)展開發(fā)者非常不友好,源代碼中提供的Zend API極其難用,API復(fù)雜而且凌亂,充斥著各種宏的寫法。Zend API坑非常多,普通開發(fā)者很容易踩到坑里。出現(xiàn)各種莫名其妙的core dump問(wèn)題。Zend API幾乎沒(méi)有任何文檔,開發(fā)者如果要真正掌握這項(xiàng)技能需要付出大量的學(xué)習(xí)時(shí)間。
于是我今年就冒出一個(gè)新的想法,基于我編寫swoole擴(kuò)展超過(guò)5年的經(jīng)驗(yàn),我試圖在Zend API和C++之間建立一個(gè)包裝層,讓PHP擴(kuò)展開發(fā)變得簡(jiǎn)單。有一定C++基礎(chǔ)的PHPer都可以輕松得開發(fā)一個(gè)PHP擴(kuò)展。
PHP-X這個(gè)項(xiàng)目就這樣誕生了,開發(fā)只用了一個(gè)月的時(shí)間。它的開發(fā)效率非常高,在我公司中一個(gè)只工作了3年的PHP 程序員,都可以做出一個(gè)擴(kuò)展來(lái)。接下來(lái)陸續(xù)在公司的幾個(gè)項(xiàng)目中進(jìn)行了快速驗(yàn)證。在3個(gè)的時(shí)間里修復(fù)了大量崩潰和內(nèi)存泄漏問(wèn)題。目前穩(wěn)定性、性能、健壯性均已達(dá)到工業(yè)級(jí)水準(zhǔn)。
0x01 起步
PHP-X本身基于C++11開發(fā),使用cmake進(jìn)行編譯配置。首先,你需要確定所有依賴項(xiàng)已安裝好。包括:
[if !supportLists]·?[endif]gcc-4.8 或更高版本
[if !supportLists]·?[endif]php-7.0 或更高版本,需要php7-dev包
[if !supportLists]·?[endif]cmake-2.8 或更高版本
然后安裝PHP-X。
git clone https://github.com/swoole/PHP-X.gitcd?PHP-X
cmake .make?-j?4
sudo make?install
未出現(xiàn)任何編譯錯(cuò)誤,會(huì)成功編譯出libphpx.so,并安裝到系統(tǒng)的lib目錄。頭文件會(huì)復(fù)制到系統(tǒng)的include目錄。這時(shí)需要執(zhí)行sudo ldconfig刷新so文件緩存。
0x02 新建工程
使用任意開發(fā)工具,新建一個(gè)test.cc源文件。首先需要引入phpx.h頭文件。然后使用using引入phpx的命名空間。PHP官方未使用C++,因此phpx直接使用了php作為命名空間。
#include <phpx.h>using?namespace?std;using?namespace?php;
創(chuàng)建擴(kuò)展使用PHPX_EXTENSION宏來(lái)實(shí)現(xiàn)。在這宏中只需要new Extension即可創(chuàng)建擴(kuò)展。構(gòu)造方法接受2個(gè)參數(shù),第一個(gè)是擴(kuò)展的名稱,第二個(gè)是擴(kuò)展的版本號(hào)。在PHPX_EXTENSION宏中return這個(gè)擴(kuò)展對(duì)象的指針。
PHPX_EXTENSION()
{
????Extension *ext = new?Extension("test", "0.0.1");
????return?ext;
}
這里必須使用new Extension,而不能直接在棧上創(chuàng)建對(duì)象
0x03 增加函數(shù)
一個(gè)PHP擴(kuò)展的主要作用就是提供擴(kuò)展函數(shù),擴(kuò)展函數(shù)由于是用C/C++代碼實(shí)現(xiàn),因此它的性能會(huì)比PHP用戶函數(shù)性能高幾十甚至上百倍。在phpx中實(shí)現(xiàn)函數(shù)非常簡(jiǎn)單。使用PHPX_FUNCTION來(lái)實(shí)現(xiàn)擴(kuò)展函數(shù),然后調(diào)用Extension::registerFunction來(lái)注冊(cè)擴(kuò)展函數(shù)。
PHPX_FN是一個(gè)助手宏,實(shí)際上展開就是"cpp_hello_world", cpp_hello_world?PHPX_FUNCTION展開后,包含了2個(gè)變量,第一個(gè)是參數(shù)args,第二個(gè)是返回值retval?通過(guò)操作args和retval兩個(gè)變量,就可以實(shí)現(xiàn)函數(shù)的輸入和輸出
這里我們的代碼非常簡(jiǎn)單,cpp_test($str, $n),調(diào)用這個(gè)函數(shù)返回一個(gè)$n個(gè)$str的數(shù)組。
#include <phpx.h>using?namespace?std;using?namespace?php;
//聲明函數(shù)
PHPX_FUNCTION(cpp_test);
PHPX_EXTENSION()
{
????Extension *ext = new?Extension("test", "0.0.1");
????ext->registerFunction(PHPX_FN(cpp_test));
????return?ext;
}
//實(shí)現(xiàn)函數(shù)
PHPX_FUNCTION(cpp_test)
{
????//args[1] 就是這個(gè)擴(kuò)展函數(shù)的第 2 個(gè)參數(shù)
????long?n = args[1].toInt();
????//將返回值 retval 初始化為數(shù)組
????Array _array(retval);
????for(int?i = 0; i < n; i++)
????{
????????//args[0] 就是這個(gè)擴(kuò)展函數(shù)的第 1 個(gè)參數(shù)
????????//append 方法表示向數(shù)組中追加元素
????????_array.append(args[0]);
????}
}
0x04 編譯擴(kuò)展
編寫一個(gè)Makefile文件。內(nèi)容如下:
PHP_INCLUDE = `php-config --includes`
PHP_LIBS = `php-config --libs`
PHP_LDFLAGS = `php-config --ldflags`
PHP_INCLUDE_DIR = `php-config --include-dir`
PHP_EXTENSION_DIR = `php-config --extension-dir`
test.so: test.cc
????c++ -DHAVE_CONFIG_H -g -o test.so -O0 -fPIC -shared test.cc -std=c++11?${PHP_INCLUDE}?-I${PHP_INCLUDE_DIR}?-lphpx
????install: test.so
????cp test.so ${PHP_EXTENSION_DIR}/
clean:
????rm *.so
php-config?這個(gè)工具是PHP提供的,使用php-config可以得到PHP的安裝路徑、頭文件目錄、擴(kuò)展目錄、其他額外的編譯參數(shù)等等。
這個(gè)Makefile支持了3個(gè)指令,make編譯,make clean清理,make install安裝到擴(kuò)展目錄中。
這里可能需要root權(quán)限,使用sudo make install進(jìn)行安裝直接從網(wǎng)頁(yè)復(fù)制,可能會(huì)出現(xiàn)tab制表符被替換為空格,請(qǐng)手工編輯一下Makefile使用tab縮進(jìn)MacOS下需要在c++編譯參數(shù)中增加-undefined dynamic_lookup
編寫好之后執(zhí)行make install,就會(huì)編譯擴(kuò)展并將擴(kuò)展test.so安裝到PHP的擴(kuò)展目錄中。這時(shí)需要修改php.ini加入extension=test.so加載擴(kuò)展。
使用php -m來(lái)觀察你的擴(kuò)展是否正常加載。
php -m
[PHP Modules]
Core
ctype
curl
date
dom
fileinfo
filter
gd
hash
iconv
inotify
json
libxml
mbstring
mcrypt
memcached
mongodb
mysqli
mysqlnd
openssl
pcntl
pcre
PDO
pdo_mysql
pdo_sqlite
Phar
posix
redis
Reflection
session
SimpleXML
sockets
SPL
sqlite3
standard
swooletesttokenizer
xml
xmlreader
xmlwriter
yac
zlib
zmq
[Zend Modules]
這里看到test,表明你的擴(kuò)展已經(jīng)加載成功了,現(xiàn)在就可以調(diào)用cpp_test這個(gè)擴(kuò)展函數(shù)了。
0x05 執(zhí)行
編寫一個(gè)test.php,內(nèi)容為:
<?php
var_dump(cpp_test("hello", 3));
執(zhí)行test.php:
php test.php
array(3) {
??[0]=>
??string(5) "hello"
??[1]=>
??string(5) "hello"
??[2]=>
??string(5) "hello"
}
可以看到執(zhí)行結(jié)果符合預(yù)期。那么恭喜你,現(xiàn)在你已經(jīng)成功地開發(fā)了一個(gè)PHP擴(kuò)展了。是不是很簡(jiǎn)單?
0x06 更多
上面的例子還比較簡(jiǎn)單,只是編寫了一個(gè)擴(kuò)展函數(shù)。要真正在實(shí)際項(xiàng)目中使用PHP-X你還有很多工作要做。
[if !supportLists]·?[endif]需要C++的功底
[if !supportLists]·?[endif]了解更多PHP-X的API另外配合使用Eclipse等IDE工具,可以實(shí)現(xiàn)API自動(dòng)提示和補(bǔ)齊,開發(fā)起來(lái)會(huì)更順手
[if !supportLists]·?[endif]學(xué)習(xí)資料加下微微老師QQ:1079192266
?PHP-X是我在2017年年初創(chuàng)建的一個(gè)新項(xiàng)目。這個(gè)項(xiàng)目的目標(biāo)就是讓有一定工作經(jīng)驗(yàn)的PHP程序都能夠具備擴(kuò)展開發(fā)的能力。
0x00 初衷
從2012年開始編寫swoole,現(xiàn)在算來(lái)已經(jīng)有5個(gè)年頭了。我發(fā)現(xiàn)編寫一個(gè)PHP 擴(kuò)展這個(gè)工作非常艱難。PHP 程序員群體中,甚至可以說(shuō) 100 人中都很難找出一個(gè)會(huì)編寫 PHP 擴(kuò)展的人來(lái)。PHP 官方對(duì)擴(kuò)展開發(fā)者非常不友好,源代碼中提供的Zend API極其難用,API復(fù)雜而且凌亂,充斥著各種宏的寫法。Zend API坑非常多,普通開發(fā)者很容易踩到坑里。出現(xiàn)各種莫名其妙的core dump問(wèn)題。Zend API幾乎沒(méi)有任何文檔,開發(fā)者如果要真正掌握這項(xiàng)技能需要付出大量的學(xué)習(xí)時(shí)間。
于是我今年就冒出一個(gè)新的想法,基于我編寫swoole擴(kuò)展超過(guò)5年的經(jīng)驗(yàn),我試圖在Zend API和C++之間建立一個(gè)包裝層,讓PHP擴(kuò)展開發(fā)變得簡(jiǎn)單。有一定C++基礎(chǔ)的PHPer都可以輕松得開發(fā)一個(gè)PHP擴(kuò)展。
PHP-X這個(gè)項(xiàng)目就這樣誕生了,開發(fā)只用了一個(gè)月的時(shí)間。它的開發(fā)效率非常高,在我公司中一個(gè)只工作了3年的PHP 程序員,都可以做出一個(gè)擴(kuò)展來(lái)。接下來(lái)陸續(xù)在公司的幾個(gè)項(xiàng)目中進(jìn)行了快速驗(yàn)證。在3個(gè)的時(shí)間里修復(fù)了大量崩潰和內(nèi)存泄漏問(wèn)題。目前穩(wěn)定性、性能、健壯性均已達(dá)到工業(yè)級(jí)水準(zhǔn)。
0x01 起步
PHP-X本身基于C++11開發(fā),使用cmake進(jìn)行編譯配置。首先,你需要確定所有依賴項(xiàng)已安裝好。包括:
[if !supportLists]·?[endif]gcc-4.8 或更高版本
[if !supportLists]·?[endif]php-7.0 或更高版本,需要php7-dev包
[if !supportLists]·?[endif]cmake-2.8 或更高版本
然后安裝PHP-X。
git clone https://github.com/swoole/PHP-X.gitcd?PHP-X
cmake .make?-j?4
sudo make?install
未出現(xiàn)任何編譯錯(cuò)誤,會(huì)成功編譯出libphpx.so,并安裝到系統(tǒng)的lib目錄。頭文件會(huì)復(fù)制到系統(tǒng)的include目錄。這時(shí)需要執(zhí)行sudo ldconfig刷新so文件緩存。
0x02 新建工程
使用任意開發(fā)工具,新建一個(gè)test.cc源文件。首先需要引入phpx.h頭文件。然后使用using引入phpx的命名空間。PHP官方未使用C++,因此phpx直接使用了php作為命名空間。
#include <phpx.h>using?namespace?std;using?namespace?php;
創(chuàng)建擴(kuò)展使用PHPX_EXTENSION宏來(lái)實(shí)現(xiàn)。在這宏中只需要new Extension即可創(chuàng)建擴(kuò)展。構(gòu)造方法接受2個(gè)參數(shù),第一個(gè)是擴(kuò)展的名稱,第二個(gè)是擴(kuò)展的版本號(hào)。在PHPX_EXTENSION宏中return這個(gè)擴(kuò)展對(duì)象的指針。
PHPX_EXTENSION()
{
????Extension *ext = new?Extension("test", "0.0.1");
????return?ext;
}
這里必須使用new Extension,而不能直接在棧上創(chuàng)建對(duì)象
0x03 增加函數(shù)
一個(gè)PHP擴(kuò)展的主要作用就是提供擴(kuò)展函數(shù),擴(kuò)展函數(shù)由于是用C/C++代碼實(shí)現(xiàn),因此它的性能會(huì)比PHP用戶函數(shù)性能高幾十甚至上百倍。在phpx中實(shí)現(xiàn)函數(shù)非常簡(jiǎn)單。使用PHPX_FUNCTION來(lái)實(shí)現(xiàn)擴(kuò)展函數(shù),然后調(diào)用Extension::registerFunction來(lái)注冊(cè)擴(kuò)展函數(shù)。
PHPX_FN是一個(gè)助手宏,實(shí)際上展開就是"cpp_hello_world", cpp_hello_world?PHPX_FUNCTION展開后,包含了2個(gè)變量,第一個(gè)是參數(shù)args,第二個(gè)是返回值retval?通過(guò)操作args和retval兩個(gè)變量,就可以實(shí)現(xiàn)函數(shù)的輸入和輸出
這里我們的代碼非常簡(jiǎn)單,cpp_test($str, $n),調(diào)用這個(gè)函數(shù)返回一個(gè)$n個(gè)$str的數(shù)組。
#include <phpx.h>using?namespace?std;using?namespace?php;
//聲明函數(shù)
PHPX_FUNCTION(cpp_test);
PHPX_EXTENSION()
{
????Extension *ext = new?Extension("test", "0.0.1");
????ext->registerFunction(PHPX_FN(cpp_test));
????return?ext;
}
//實(shí)現(xiàn)函數(shù)
PHPX_FUNCTION(cpp_test)
{
????//args[1] 就是這個(gè)擴(kuò)展函數(shù)的第 2 個(gè)參數(shù)
????long?n = args[1].toInt();
????//將返回值 retval 初始化為數(shù)組
????Array _array(retval);
????for(int?i = 0; i < n; i++)
????{
????????//args[0] 就是這個(gè)擴(kuò)展函數(shù)的第 1 個(gè)參數(shù)
????????//append 方法表示向數(shù)組中追加元素
????????_array.append(args[0]);
????}
}
0x04 編譯擴(kuò)展
編寫一個(gè)Makefile文件。內(nèi)容如下:
PHP_INCLUDE = `php-config --includes`
PHP_LIBS = `php-config --libs`
PHP_LDFLAGS = `php-config --ldflags`
PHP_INCLUDE_DIR = `php-config --include-dir`
PHP_EXTENSION_DIR = `php-config --extension-dir`
test.so: test.cc
????c++ -DHAVE_CONFIG_H -g -o test.so -O0 -fPIC -shared test.cc -std=c++11?${PHP_INCLUDE}?-I${PHP_INCLUDE_DIR}?-lphpx
????install: test.so
????cp test.so ${PHP_EXTENSION_DIR}/
clean:
????rm *.so
php-config?這個(gè)工具是PHP提供的,使用php-config可以得到PHP的安裝路徑、頭文件目錄、擴(kuò)展目錄、其他額外的編譯參數(shù)等等。
這個(gè)Makefile支持了3個(gè)指令,make編譯,make clean清理,make install安裝到擴(kuò)展目錄中。
這里可能需要root權(quán)限,使用sudo make install進(jìn)行安裝直接從網(wǎng)頁(yè)復(fù)制,可能會(huì)出現(xiàn)tab制表符被替換為空格,請(qǐng)手工編輯一下Makefile使用tab縮進(jìn)MacOS下需要在c++編譯參數(shù)中增加-undefined dynamic_lookup
編寫好之后執(zhí)行make install,就會(huì)編譯擴(kuò)展并將擴(kuò)展test.so安裝到PHP的擴(kuò)展目錄中。這時(shí)需要修改php.ini加入extension=test.so加載擴(kuò)展。
使用php -m來(lái)觀察你的擴(kuò)展是否正常加載。
php -m
[PHP Modules]
Core
ctype
curl
date
dom
fileinfo
filter
gd
hash
iconv
inotify
json
libxml
mbstring
mcrypt
memcached
mongodb
mysqli
mysqlnd
openssl
pcntl
pcre
PDO
pdo_mysql
pdo_sqlite
Phar
posix
redis
Reflection
session
SimpleXML
sockets
SPL
sqlite3
standard
swooletesttokenizer
xml
xmlreader
xmlwriter
yac
zlib
zmq
[Zend Modules]
這里看到test,表明你的擴(kuò)展已經(jīng)加載成功了,現(xiàn)在就可以調(diào)用cpp_test這個(gè)擴(kuò)展函數(shù)了。
0x05 執(zhí)行
編寫一個(gè)test.php,內(nèi)容為:
<?php
var_dump(cpp_test("hello", 3));
執(zhí)行test.php:
php test.php
array(3) {
??[0]=>
??string(5) "hello"
??[1]=>
??string(5) "hello"
??[2]=>
??string(5) "hello"
}
可以看到執(zhí)行結(jié)果符合預(yù)期。那么恭喜你,現(xiàn)在你已經(jīng)成功地開發(fā)了一個(gè)PHP擴(kuò)展了。是不是很簡(jiǎn)單?
0x06 更多
上面的例子還比較簡(jiǎn)單,只是編寫了一個(gè)擴(kuò)展函數(shù)。要真正在實(shí)際項(xiàng)目中使用PHP-X你還有很多工作要做。
[if !supportLists]·?[endif]需要C++的功底
[if !supportLists]·?[endif]了解更多PHP-X的API另外配合使用Eclipse等IDE工具,可以實(shí)現(xiàn)API自動(dòng)提示和補(bǔ)齊,開發(fā)起來(lái)會(huì)更順手
[if !supportLists]·?[endif]學(xué)習(xí)資料加下微微老師QQ:1079192266