一. 摘要
PHP Extension是扩展PHP的主要手段,如数据库访问,序列化,或者远程过程调用,使用过PHP的人,其实都使用过PHP Extension,PHP里面很多的函数也是通过扩展实现的,而在PHP源码中包含了几十个扩展,PECL仓库中也提供了上百个扩展,所以只要使用 PHP开发,就不可避免的要开发PHP Extension。本文会用图文并茂的方式一步一步为大家介绍如何开发一个PHP Extension。
开发环境:Ubuntu 10.10,PHP 5.3.5,有一个可运行PHP的Web服务器(Nginx或Apache)
要求:了解C语言基础,了解PHP编程
需求:开发一个名为fetion_echo的Extension,只有一个简单的say_goodbye()函数,输入一个字符串,该函数返回"Goodbye xxx"。
开发PHP Extension的过程基本可以分为如下几步:?
1. 生成扩展框架?
2. Unix Build System配置?
3. 编写phpinfo()回调函数?
4. 编写核心代码?
5. 配置、编译?
6. 配置php.ini
二. 生成扩展框架
下载PHP源代码,我使用的是PHP 5.3.5。进入PHP源代码目录可以看到有个ext目录,这里是和PHP Extension有关,使用ls命令查看,可以看到很多已经存在的PHP Extension,如pdo_mysql,json等,如图1所示:
注意在该目录下有一个ext_skel脚本文件,接下来我们将会用它来生成Linux环境下PHP Extension代码框架,而ext_skel_win32.php是windows下的生成脚本。ext_skel完整的命令格式为:./ext_skel --extname=module [--proto=file] [--stubs=file] [--xml[=file]]
[--skel=dir] [--full-xml] [--no-help]
对其中的参数解释如下:?
--extname:参数指定了Extension的名字?
--no-help:指定在生成的代码框架中不加入注释等,除非我们对于开发PHP Extension非常有经验,否则还是不要指定该参数。其他的参数我们暂时可以不用考虑。
接下来在ext目录下输入如下命令:$ ./ext_skel --extname=fetion_echo
再次使用ls命令查看ext目录,可以看到多了一个名为fetion_echo的目录,进入该目录可以看到ext_skel已经为我们建立好了PHP Extension的基本框架,如图2所示:
在这里有几个比较重要的文件,我们需要介绍一下:?
config.m4:Linux下的Build System配置文件,会使用它来生成configure文件和makefile。?
php_fetion_echo.h:扩展模块的头文件。?
fetion_echo.c:扩展模块的主程序文件,如果我们的扩展模块中有多个函数,最终所有的函数入口都在该文件中。
三. Unix Build System配置
config.m4文件告诉Unix Build System我们的扩展支持什么configure选项,使用Emacs或者vim打开该文件,大家可以看到一堆不认识的配置,不过不用担心,因为该文件 中以"dnl"开头的都是注释,暂时不用考虑。我们能用到只有下面几行:dnl If your extension references something external, use with:
dnl PHP_ARG_WITH(fetion_echo, for fetion_echo support,
dnl Make sure that the comment is aligned:
dnl [ --with-fetion_echo Include fetion_echo support])
dnl Otherwise use enable:
dnl PHP_ARG_ENABLE(fetion_echo, whether to enable fetion_echo support,
dnl Make sure that the comment is aligned:
dnl [ --enable-fetion_echo Enable fetion_echo support])
其实大家都能看明白,如果扩展使用了一些外部的引用,就使用下面的三行,否则使用最后面的三行,由于我们只是开发一个简单的echo扩展,没有使用任何外部引用,只要取消掉最后三行的注释就行:PHP_ARG_ENABLE(fetion_echo, whether to enable fetion_echo support,
Make sure that the comment is aligned:
[ --enable-fetion_echo Enable fetion_echo support])?
保存退出,至此配置文件就完成了。
四. 分析PHP Extension核心代码
为了下面更好的介绍,这里先简单的介绍PHP Extension核心代码,打开fetion_echo.c文件,可以看到如下一段代码:/* {{{ fetion_echo_module_entry */
zend_module_entry fetion_echo_module_entry = {
#if ZEND_MODULE_API_NO >= 20010901
STANDARD_MODULE_HEADER,
#endif
"fetion_echo",
fetion_echo_functions,
PHP_MINIT(fetion_echo),
PHP_MSHUTDOWN(fetion_echo),
PHP_RINIT(fetion_echo),
PHP_RSHUTDOWN(fetion_echo),
PHP_MINFO(fetion_echo),
#if ZEND_MODULE_API_NO >= 20010901
"0.1",
#endif
STANDARD_MODULE_PROPERTIES
};
这里初始化了一个C语言中的结构体,每个PHP Extension其实就是一个zend_module_entry结构体,在该结构体中定义了每个扩展所需的字段,大家可以通过查看 zend_module_entry源代码看到。就本例而言,我们简单的介绍一下上面的代码:
1. STANDARD_MODULE_HEADER:C语言的宏,用来初始化zend_module_entry的前几个字段,包括结构体大小等?
2. fetion_echo:指定了扩展的名字,对应结构体中的name字段?
3. fetion_echo_functions:一个zend_function_entry类型的数组,指向扩展的函数表,所有需要暴露给用户的函数都需要在该函数表中注册?
4. PHP_MINIT(fetion_echo):模块初始化回调函数,在扩展被加载时调用,MINIT = Module Initialization?
5. PHP_MSHUTDOWN(fetion_echo):模块卸载回调函数,在扩展杯卸载时调用,MSHUTDOWN = Module Shutdown?
6. PHP_RINIT(fetion_echo):请求初始化回调函数,每个请求开始时调用,RINIT = Request Initialization?
7. PHP_RSHUTDOWN(fetion_echo):请求结束回调函数,每个请求结束时调用,RSHUTDOWN = Request Shutdown?
8. PHP_MINFO(fetion_echo):扩展信息函数,在phpinfo()函数中会调用,用于显示模块的自定义信息?
9. 0.1:指定了扩展的版本号,对应结构体中的version字段?
10. STANDARD_MODULE_PROPERTIES:C语言的宏,用来初始化zend_module_entry的后几个字段
大家可以看到,在4-8我们指定了4个回调函数,这四个函数可以说我们提供了一种注入机制,让我们能够在这几个关键点进行资源的初始化或者资源回 收。另外,需要说明的一点是,所有的回调函数我们都是通过Zend提供的宏定义的,主要是为了防止在PHP运行时的命名冲突问题,事实上不仅仅是函数,包 括函数返回值、全局变量等我们都会使用这种方式。
五. 编写phpinfo()回调函数?
打开fetion_echo.c文件,在PHP_MINFO_FUNCTION里面编写如下代码:PHP_MINFO_FUNCTION(fetion_echo) {
php_info_print_table_start();
php_info_print_table_header(2, "fetion_echo support", "enabled");
php_info_print_table_row(2, "author", "TerryLee");
php_info_print_table_row(2, "version", "0.1");
php_info_print_table_end();
/* Remove comments if you have entries in php.ini
DISPLAY_INI_ENTRIES();
*/
}
这里主要是phpinfo()函数调用时显示自定义信息,用到了四个函数:
1. php_info_print_table_start():定义phpinfo表格开始?
2. php_info_print_table_header():定义phpinfo表格头,第一个参数指定列数,后面指定与第一个参数数量相等的自定义文字信息?
3. php_info_print_table_row():定义phpinfo表格内容,第一个参数指定列数,后面指定与第一个参数数量相等的自定义文字信息?
4. php_info_print_table_end():定义phpinfo表格结尾
在本例中我们定义了表格头,指定扩展是否可用;另外定义了两行内容,指定扩展的作者和版本。
六. 编写核心代码
接下来是时候编写我们的扩展核心代码了,打开php_fetion_echo.h文件,添加一行声明:PHP_FUNCTION(say_goodbye);
注意这里不是用"原生"的编写C语言函数的方式,而是通过PHP_FUNCTION宏定义(具体原因前面讲过),say_goodbye是我们开发的扩展模块要暴露给用户的函数名称。
打开fetion_echo.c,在这里实现say_goodbye函数:PHP_FUNCTION(say_goodbye) {
char *arg = NULL;
int arg_len, len;
char *strg;
if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s",
&arg, &arg_len) == FAILURE) {
return;
}
len = spprintf(&strg, 0, "Goodbye %s\n", arg);
RETURN_STRINGL(strg, len, 0);
}
里面的具体实现很简单,接收到参数之后,返回"Goodybye 参数"字符串,需要解释的是:
1. 参数接收:这里接收函数的参数需要通过zend_parse_parameter函数解析,第一个参数指定用户传入say_goodbye函数的参数个 数,可以通过宏ZEND_NUM_ARGS()生成,TSRMLS_CC用来确保线程安全;第二个参数是一个字符串,每个字母代表一种类型,其中"s"代 表char*或者int类型,"b"代表布尔类型,"l"代表long类型,完整的类型映射可以看这里;后面几个参数是我们定义的局部变量,用来接收传入的参数值
2. 函数返回值:不能使用C语言原生的return语句,而应该使用Zend API里提供的宏定义,如RETURN_STRINGL返回一个字符串;而RETURN_TRUE返回布尔类型true。
声明扩展函数参数信息(要写在头文件),我们的函数原型为say_goodbye(name),声明参数方式:ZEND_BEGIN_ARG_INFO(arg_say_goodbye, 0)
ZEND_ARG_INFO(0, name)
ZEND_END_ARG_INFO()
这里都是Zend API提供的宏定义,在后面我会专门介绍扩展函数参数声明。实现完say_goodbye函数之后,我们再注册该函数到函数表 fetion_echo_functions(前面介绍过),第一个参数为函数名,第二个参数为函数参数数组信息,如下代码所示:const zend_function_entry fetion_echo_functions[] = {
PHP_FE(say_goodbye, arg_say_goodbye)
{NULL, NULL, NULL}
};
注意最后一行{NULL, NULL, NULL}是必须的,只有注册到函数表中的函数才能暴露给用户使用。
七. 配置、编译、安装?
在fetion_echo目录下输入如下命令,具体路径请根据自己安装的PHP路径设置:/usr/local/php5/bin/phpize
注意如果没有安装过m4和autoconf,请先使用下列命令安装 :sudo apt-get install m4
sudo apt-get install autoconf
运行phpize之后,再用ls命令可以看到在fetion_echo目录生成了很多的文件,包括configure文件:
下面就可以安装该扩展了,使用Linux下面的"标准"三步安装模式:./configure
或者: ./configure --with-php-config=/usr/local/php/bin/php-config
make
make install
在安装完成后会提示具体的扩展安装路径,然后就可以把该扩展加入到php.ini配置中,注意extension_dir的设置,重启Web服务器。
/etc/init.d/php-fpm restart
八. 运行?
上面步骤都完成后,运行phpinfo()应该可以看到:
编写一个简单的测试脚本,如下所示:<?php
echo say_goodbye("Foo");
?>?
在浏览器里面查看:
如果您能够看到该界面,说明扩展已经工作正常了。
九. 总结
本文通过一个简单的示例,为大家介绍了如何使用Zend API和C语言在Linux下开发一个PHP Extension。下一篇我们看一下如何开发一个简单的类扩展。
参考资料
1. http://www.php.net/manual/en/internals2.buildsys.configunix.php?
2. http://www.php.net/manual/en/internals2.buildsys.skeleton.php