PHP扩展开发(1):快速入门

一. 摘要

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文件,可以看到如下一段代码:

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(); 
}

这里主要是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 make make install

在安装完成后会提示具体的扩展安装路径,然后就可以把该扩展加入到php.ini配置中,注意extension_dir的设置,重启Web服务器。

八. 运行 

上面步骤都完成后,运行phpinfo()应该可以看到:

编写一个简单的测试脚本,在浏览器里面查看

如果您能够看到该界面,说明扩展已经工作正常了。

九. 总结

本文通过一个简单的示例,为大家介绍了如何使用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

发表评论

电子邮件地址不会被公开。 必填项已用*标注