使用C来开发PHP扩展可以极大的提高运行率,但扩展的开发比较复杂、开发周期长,所以,个中利弊还是要自己权衡。
本文只是开发PHP扩展的一个简单示范,演示一下PHP扩展开发的一般流程,并不深入研究相关的API。

第一步、获取源代码

开发扩展我们首先需要一份与你所使用的PHP版本对应的PHP源代码,因为PHP的源代码中有我们所需要的扩展开发环境。

在这里,我使用的是PHP-5.5.33的源代码。

第二步、生成扩展框架

进入PHP源代码目录下的ext目录,该目录下有一个名为ext_skel的文件,我们需要用这个文件生成扩展框架。

ext_skel文件有以下选项

1
2
3
4
5
6
7
8
9
10
11
12
./ext_skel --extname=module [--proto=file] [--stubs=file] [--xml[=file]]
[--skel=dir] [--full-xml] [--no-help]

--extname=module module is the name of your extension
--proto=file file contains prototypes of functions to create
--stubs=file generate only function stubs in file
--xml generate xml documentation to be added to phpdoc-cvs
--skel=dir path to the skeleton directory
--full-xml generate xml documentation for a self-contained extension
(not yet implemented)
--no-help don't try to be nice and create comments in the code
and helper functions to test if the module compiled

我们要创建一个名为dump的函数,所以终端输入命令:

1
$ ./ext_skel --extname=dump

此时,目录下生成了一个名为dump的目录,该目录就是我们的扩展开发环境,其结构如下:

1
2
3
4
5
6
7
8
9
├── config.m4
├── config.w32
├── CREDITS
├── dump.c
├── dump.php
├── EXPERIMENTAL
├── php_dump.h
└── tests
└── 001.phpt

第三步、更改配置文件

先要修改config.m4文件,将以下三行前面的注释(dnl)去掉:

1
2
3
dnl PHP_ARG_ENABLE(dump, whether to enable dump support,
dnl Make sure that the comment is aligned:
dnl [ --enable-dump Enable dump support])

第四步、添加函数声明

在php_dump.h的47行:

1
PHP_FUNCTION(confirm_dump_compiled);	/* For testing, remove later. */

添加一行我们自己的函数声明:

1
PHP_FUNCTION(dump);

第五步、实现相应功能

在dump.c的41行:

1
2
3
4
const zend_function_entry dump_functions[] = {
PHP_FE(confirm_dump_compiled, NULL) /* For testing, remove later. */
PHP_FE_END /* Must be the last line in dump_functions[] */
};

中添加一行,使其变为:

1
2
3
4
5
const zend_function_entry dump_functions[] = {
PHP_FE(confirm_dump_compiled, NULL) /* For testing, remove later. */
PHP_FE(dump, NULL)
PHP_FE_END /* Must be the last line in dump_functions[] */
};

在dump.c文件的末尾添加PHP_FUNCTION(dump)函数的实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
/* 核心代码 */
PHP_FUNCTION(dump)
{
zval *zv_ptr;

if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "z", &zv_ptr) == FAILURE) {
return;
}

switch (Z_TYPE_P(zv_ptr)) {
case IS_NULL:
php_printf("NULL: null\n");
break;
case IS_BOOL:
if (Z_BVAL_P(zv_ptr)) {
php_printf("BOOL: true\n");
} else {
php_printf("BOOL: false\n");
}
break;
case IS_LONG:
php_printf("LONG: %ld\n", Z_LVAL_P(zv_ptr));
break;
case IS_DOUBLE:
php_printf("DOUBLE: %g\n", Z_DVAL_P(zv_ptr));
break;
case IS_STRING:
php_printf("STRING: value=\"");
PHPWRITE(Z_STRVAL_P(zv_ptr), Z_STRLEN_P(zv_ptr));
php_printf("\", length=%d\n", Z_STRLEN_P(zv_ptr));
break;
case IS_RESOURCE:
php_printf("RESOURCE: id=%ld\n", Z_RESVAL_P(zv_ptr));
break;
case IS_ARRAY:
php_printf("ARRAY: hashtable=%p\n", Z_ARRVAL_P(zv_ptr));
break;
case IS_OBJECT:
php_printf("OBJECT: ???\n");
break;
}
}

第六步、编译安装扩展

在dump目录下运行:

1
2
3
$ /path/to/phpize
$ ./configure --with-php-config=/path/to/php-config
$ sudo make && sudo make install

phpize命令用来准备PHP扩展的编译环境, php-config指明了当前PHP环境的一些信息。

第七步、修改PHP.ini

在php.ini中添加

1
extension=dump.so

而后,重启php-fpm服务

第八步、测试

test.php
1
2
3
4
<?php

$arr = array(1, 2, 3);
echo dump($arr);

运行后输出:

1
ARRAY: hashtable=0x7f4dbf723278

证明扩展载入成功,这样,我们就开发完了一个简单的扩展。