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`作为命名空间。 ``` <span class="hljs-meta">#<span class="hljs-meta-keyword">include</span> <span class="hljs-meta-string"><phpx.h></phpx.h></span></span> <span class="hljs-keyword">using</span> <span class="hljs-keyword">namespace</span> <span class="hljs-built_in">std</span>; <span class="hljs-keyword">using</span> <span class="hljs-keyword">namespace</span> 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`的数组。 ``` <span class="hljs-meta">#<span class="hljs-meta-keyword">include</span> <span class="hljs-meta-string"><phpx.h></phpx.h></span></span> <span class="hljs-keyword">using</span> <span class="hljs-keyword">namespace</span> <span class="hljs-built_in">std</span>; <span class="hljs-keyword">using</span> <span class="hljs-keyword">namespace</span> php; <span class="hljs-comment">//声明函数</span> PHPX_FUNCTION(cpp_test); PHPX_EXTENSION() { Extension *ext = <span class="hljs-keyword">new</span> Extension(<span class="hljs-string">"test"</span>, <span class="hljs-string">"0.0.1"</span>); ext->registerFunction(PHPX_FN(cpp_test)); <span class="hljs-keyword">return</span> ext; } <span class="hljs-comment">//实现函数</span> PHPX_FUNCTION(cpp_test) { <span class="hljs-comment">//args[1] 就是这个扩展函数的第 2 个参数</span> <span class="hljs-keyword">long</span> n = args[<span class="hljs-number">1</span>].toInt(); <span class="hljs-comment">//将返回值 retval 初始化为数组</span> Array _array(retval); <span class="hljs-keyword">for</span>(<span class="hljs-keyword">int</span> i = <span class="hljs-number">0</span>; i //args[0] 就是这个扩展函数的第 1 个参数 <span class="hljs-comment">//append 方法表示向数组中追加元素</span> _array.append(args[<span class="hljs-number">0</span>]); } }
0x04 编译扩展
编写一个Makefile文件。内容如下: `
PHP_INCLUDE = php-config --includes
PHP_LIBS = php-config --libs
PHP_LDFLAGS = php-config --ldflags
PHP_INCLUDE_DIR = php-config --<span class="hljs-keyword">include</span>-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++<span class="hljs-number">11</span> <span class="hljs-variable">${PHP_INCLUDE}</span> -I<span class="hljs-variable">${PHP_INCLUDE_DIR}</span> -lphpx
install: test.so
cp test.so <span class="hljs-variable">${PHP_EXTENSION_DIR}</span>/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
<span class="hljs-keyword">test
</span>tokenizer
xml
xmlreader
xmlwriter
yac
zlib
zmq
[Zend Modules]这里看到test,表明你的扩展已经加载成功了,现在就可以调用cpp_test这个扩展函数了。 0x05 执行
编写一个test.php,内容为: `
执行`test.php`: ```
php test.php
array(<span class="hljs-number">3</span>) {
[<span class="hljs-number">0</span>]=>
<span class="hljs-type">string</span>(<span class="hljs-number">5</span>) <span class="hljs-string">"hello"</span>
[<span class="hljs-number">1</span>]=>
<span class="hljs-type">string</span>(<span class="hljs-number">5</span>) <span class="hljs-string">"hello"</span>
[<span class="hljs-number">2</span>]=>
<span class="hljs-type">string</span>(<span class="hljs-number">5</span>) <span class="hljs-string">"hello"</span>
}可以看到执行结果符合预期。那么恭喜你,现在你已经成功地开发了一个PHP扩展了。是不是很简单? 0x06 更多
上面的例子还比较简单,只是编写了一个扩展函数。要真正在实际项目中使用PHP-X你还有很多工作要做。 - 需要C++的功底
了解更多
PHP-X的 API另外配合使用
Eclipse等IDE工具,可以实现API自动提示和补齐,开发起来会更顺手。 相比Zend API,PHP-X要简单易用地多了,相信你不会花太多时间就可以掌握此项技能。在接下来我会撰写更多教程,教大家如何使用PHP-X实现扩展类、资源、回调函数等更复杂的功能。
评论1
暂时没有评论