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
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
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
,内容为:
执行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
实现扩展类、资源、回调函数等更复杂的功能。