PHP-X
是我在2017
年年初建立的一個新專案。這個專案的目標就是讓有一定工作經驗的PHP
程式都能夠具備擴充套件開發的能力。
0x00 初衷
從2012
年開始編寫swoole
,現在算來已經有5
個年頭了。我發現編寫一個 PHP 擴充套件這個工作非常艱難。PHP 程式設計師群體中,甚至可以說 100 人中都很難找出一個會編寫 PHP 擴充套件的人來。PHP 官方對擴充套件開發者非常不友好,原始碼中提供的Zend API
極其難用,API
複雜而且凌亂,充斥著各種巨集的寫法。Zend API
坑非常多,普通開發者很容易踩到坑裡。出現各種莫名其妙的core dump
問題。Zend API
幾乎沒有任何文件,開發者如果要真正掌握這項技能需要付出大量的學習時間。
於是我今年就冒出一個新的想法,基於我編寫swoole
擴充套件超過5
年的經驗,我試圖在Zend API
和C++
之間建立一個包裝層,讓PHP
擴充套件開發變得簡單。有一定C++
基礎的PHPer
都可以輕鬆得開發一個PHP
擴充套件。
PHP-X
這個專案就這樣誕生了,開發只用了一個月的時間。它的開發效率非常高,在我公司中一個只工作了3
年的 PHP 程式設計師,都可以做出一個擴充套件來。接下來陸續在公司的幾個專案中進行了快速驗證。在3
個的時間裡修復了大量崩潰和記憶體洩漏問題。目前穩定性、效能、健壯性均已達到工業級水準。
0x01 起步
PHP-X
本身基於C++11
開發,使用cmake
進行編譯配置。首先,你需要確定所有依賴項已安裝好。包括:
- gcc-4.8 或更高版本
- php-7.0 或更高版本,需要
php7-dev
包 - cmake-2.8 或更高版本
然後安裝PHP-X
。
git clone https://github.com/swoole/PHP-X.git
cd PHP-X
cmake .
make -j 4
sudo make install
未出現任何編譯錯誤,會成功編譯出libphpx.so
,並安裝到系統的lib
目錄。標頭檔案會複製到系統的include
目錄。這時需要執行 sudo ldconfig
重新整理so
檔案快取。
0x02 新建工程
使用任意開發工具,新建一個test.cc
原始檔。首先需要引入phpx.h
標頭檔案。然後使用using
引入phpx
的名稱空間。PHP
官方未使用C++
,因此phpx
直接使用了php
作為名稱空間。
#include <phpx.h>
using namespace std;
using namespace php;
建立擴充套件使用PHPX_EXTENSION
巨集來實現。在這巨集中只需要new Extension
即可建立擴充套件。構造方法接受2
個引數,第一個是擴充套件的名稱,第二個是擴充套件的版本號。在PHPX_EXTENSION
巨集中return
這個擴充套件物件的指標。
PHPX_EXTENSION()
{
Extension *ext = new Extension("test", "0.0.1");
return ext;
}
這裡必須使用
new Extension
,而不能直接在棧上建立物件
0x03 增加函式
一個PHP
擴充套件的主要作用就是提供擴充套件函式,擴充套件函式由於是用C/C++
程式碼實現,因此它的效能會比PHP
使用者函式效能高几十甚至上百倍。在phpx
中實現函式非常簡單。使用PHPX_FUNCTION
來實現擴充套件函式,然後呼叫Extension::registerFunction
來註冊擴充套件函式。
PHPX_FN
是一個助手巨集,實際上展開就是"cpp_hello_world", cpp_hello_world
PHPX_FUNCTION
展開後,包含了2
個變數,第一個是引數args
,第二個是返回值retval
通過操作args
和retval
兩個變數,就可以實現函式的輸入和輸出
這裡我們的程式碼非常簡單,cpp_test($str, $n)
,呼叫這個函式返回一個$n
個$str
的陣列。
#include <phpx.h>
using namespace std;
using namespace php;
//宣告函式
PHPX_FUNCTION(cpp_test);
PHPX_EXTENSION()
{
Extension *ext = new Extension("test", "0.0.1");
ext->registerFunction(PHPX_FN(cpp_test));
return ext;
}
//實現函式
PHPX_FUNCTION(cpp_test)
{
//args[1] 就是這個擴充套件函式的第 2 個引數
long n = args[1].toInt();
//將返回值 retval 初始化為陣列
Array _array(retval);
for(int i = 0; i < n; i++)
{
//args[0] 就是這個擴充套件函式的第 1 個引數
//append 方法表示向陣列中追加元素
_array.append(args[0]);
}
}
0x04 編譯擴充套件
編寫一個Makefile
檔案。內容如下:
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
這個工具是PHP
提供的,使用php-config
可以得到PHP
的安裝路徑、標頭檔案目錄、擴充套件目錄、其他額外的編譯引數等等。
這個Makefile
支援了3
個指令,make
編譯,make clean
清理,make install
安裝到擴充套件目錄中。
這裡可能需要
root
許可權,使用sudo make install
進行安裝
直接從網頁複製,可能會出現tab
製表符被替換為空格,請手工編輯一下Makefile
使用tab
縮排MacOS
下需要在c++
編譯引數中增加-undefined dynamic_lookup
編寫好之後執行make install
,就會編譯擴充套件並將擴充套件test.so
安裝到PHP
的擴充套件目錄中。這時需要修改php.ini
加入extension=test.so
載入擴充套件。
使用php -m
來觀察你的擴充套件是否正常載入。
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
swoole
test
tokenizer
xml
xmlreader
xmlwriter
yac
zlib
zmq
[Zend Modules]
這裡看到test
,表明你的擴充套件已經載入成功了,現在就可以呼叫cpp_test
這個擴充套件函式了。
0x05 執行
編寫一個test.php
,內容為:
<?php
var_dump(cpp_test("hello", 3));
執行test.php
:
php test.php
array(3) {
[0]=>
string(5) "hello"
[1]=>
string(5) "hello"
[2]=>
string(5) "hello"
}
可以看到執行結果符合預期。那麼恭喜你,現在你已經成功地開發了一個PHP
擴充套件了。是不是很簡單?
0x06 更多
上面的例子還比較簡單,只是編寫了一個擴充套件函式。要真正在實際專案中使用PHP-X
你還有很多工作要做。
- 需要
C++
的功底 - 瞭解更多
PHP-X
的 API
另外配合使用Eclipse
等IDE
工具,可以實現API
自動提示和補齊,開發起來會更順手。
相比Zend API
,PHP-X
要簡單易用地多了,相信你不會花太多時間就可以掌握此項技能。在接下來我會撰寫更多教程,教大家如何使用PHP-X
實現擴充套件類、資源、回撥函式等更復雜的功能。