如何基于 PHP-X 快速开发一个 PHP 扩展?

0x01 起步

PHP-X本身基于C++11开发,使用cmake进行编译配置。首先,你需要确定所有依赖项已安装好。包括:

  • gcc-4.8 或更高版本
  • PHP7.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的数组。

 1 #include <phpx.h>
 2 using namespace std;
 3 using namespace php;
 4 
 5 //声明函数
 6 PHPX_FUNCTION(cpp_test);
 7 
 8 PHPX_EXTENSION()
 9 {
10     Extension *ext = new Extension("test", "0.0.1");
11     ext->registerFunction(PHPX_FN(cpp_test));
12     return ext;
13 }
14 
15 //实现函数
16 PHPX_FUNCTION(cpp_test)
17 {
18     //args[1] 就是这个扩展函数的第 2 个参数
19     long n = args[1].toInt();
20     
21     //将返回值 retval 初始化为数组
22     Array _array(retval);
23     
24     for(int i = 0; i < n; i++)
25     {
26         //args[0] 就是这个扩展函数的第 1 个参数
27         //append 方法表示向数组中追加元素
28         _array.append(args[0]);
29     }
30 }

0x04 编译扩展

编写一个Makefile文件。内容如下:

 1 PHP_INCLUDE = `php-config --includes`
 2 PHP_LIBS = `php-config --libs`
 3 PHP_LDFLAGS = `php-config --ldflags`
 4 PHP_INCLUDE_DIR = `php-config --include-dir`
 5 PHP_EXTENSION_DIR = `php-config --extension-dir`
 6 
 7 test.so: test.cc
 8     c++ -DHAVE_CONFIG_H -g -o test.so -O0 -fPIC -shared test.cc -std=c++11 ${PHP_INCLUDE} -I${PHP_INCLUDE_DIR} -lphpx
 9     
10 install: test.so
11     cp test.so ${PHP_EXTENSION_DIR}/
12 clean:
13     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来观察你的扩展是否正常加载。

 1 php -m
 2 [PHP Modules]
 3 Core
 4 ctype
 5 curl
 6 date
 7 dom
 8 fileinfo
 9 filter
10 gd
11 hash
12 iconv
13 inotify
14 json
15 libxml
16 mbstring
17 mcrypt
18 memcached
19 mongodb
20 mysqli
21 mysqlnd
22 openssl
23 pcntl
24 pcre
25 PDO
26 pdo_mysql
27 pdo_sqlite
28 Phar
29 posix
30 redis
31 Reflection
32 session
33 SimpleXML
34 sockets
35 SPL
36 sqlite3
37 standard
38 swoole
39 test
40 tokenizer
41 xml
42 xmlreader
43 xmlwriter
44 yac
45 zlib
46 zmq
47 
48 [Zend Modules]

这里看到test,表明你的扩展已经加载成功了,现在就可以调用cpp_test这个扩展函数了。

0x05 执行

编写一个test.php,内容为:

 1 <?php
 2 var_dump(cpp_test("hello", 3));
 3 执行test.php:
 4 
 5 php test.php
 6 array(3) {
 7   [0]=>
 8   string(5) "hello"
 9   [1]=>
10   string(5) "hello"
11   [2]=>
12   string(5) "hello"
13 }

可以看到执行结果符合预期。那么恭喜你,现在你已经成功地开发了一个PHP扩展了。是不是很简单?

0x06 更多

上面的例子还比较简单,只是编写了一个扩展函数。要真正在实际项目中使用PHP-X你还有很多工作要做。

  • 需要C++的功底
  • 了解更多PHP-X的 API

另外配合使用Eclipse等IDE工具,可以实现API自动提示和补齐,开发起来会更顺手。

相比Zend API,PHP-X要简单易用地多了,相信你不会花太多时间就可以掌握此项技能。在接下来我会撰写更多教程,教大家如何使用PHP-X实现扩展类、资源、回调函数等更复杂的功能。