PHP 扩展开发的文章,我均已更新至《TIPI》(下面的博文可能已经过时,以 TIPI 上的内容为准)。
安装PHP7
我现在 github 上下的最新版,然后没安装太多的扩展,因为我仅仅是作为开发调试使用嘛~
[root@localhost php-src-php-7.0.3]# ./buildconf You should not run buildconf in a release package. use buildconf --force to override this check. [root@localhost php-src-php-7.0.3]# ./buildconf --force ... [root@localhost php-src-php-7.0.3]# ./configure --prefix=/usr/local/php7 --with-config-file-path=/usr/local/php7/etc --enable-fpm --with-fpm-user=www --enable-debug ... Thank you for using PHP. config.status: creating php7.spec config.status: creating main/build-defs.h config.status: creating scripts/phpize config.status: creating scripts/man1/phpize.1 config.status: creating scripts/php-config config.status: creating scripts/man1/php-config.1 config.status: creating sapi/cli/php.1 config.status: creating sapi/fpm/php-fpm.conf config.status: creating sapi/fpm/www.conf config.status: creating sapi/fpm/init.d.php-fpm config.status: creating sapi/fpm/php-fpm.service config.status: creating sapi/fpm/php-fpm.8 config.status: creating sapi/fpm/status.html config.status: creating sapi/cgi/php-cgi.1 config.status: creating ext/phar/phar.1 config.status: creating ext/phar/phar.phar.1 config.status: creating main/php_config.h config.status: executing default commands [root@localhost php-src-php-7.0.3]# make [root@localhost php-src-php-7.0.3]# make install Installing shared extensions: /usr/local/php7/lib/php/extensions/debug-non-zts-20151012/ Installing PHP CLI binary: /usr/local/php7/bin/ Installing PHP CLI man page: /usr/local/php7/php/man/man1/ Installing PHP FPM binary: /usr/local/php7/sbin/ Installing PHP FPM config: /usr/local/php7/etc/ Installing PHP FPM man page: /usr/local/php7/php/man/man8/ Installing PHP FPM status page: /usr/local/php7/php/php/fpm/ Installing phpdbg binary: /usr/local/php7/bin/ Installing phpdbg man page: /usr/local/php7/php/man/man1/ Installing PHP CGI binary: /usr/local/php7/bin/ Installing PHP CGI man page: /usr/local/php7/php/man/man1/ Installing build environment: /usr/local/php7/lib/php/build/ Installing header files: /usr/local/php7/include/php/ Installing helper programs: /usr/local/php7/bin/ program: phpize program: php-config Installing man pages: /usr/local/php7/php/man/man1/ page: phpize.1 page: php-config.1 Installing PEAR environment: /usr/local/php7/lib/php/ --2016-03-02 11:43:32-- https://pear.php.net/install-pear-nozlib.phar Resolving pear.php.net... 109.203.101.62 Connecting to pear.php.net|109.203.101.62|:443... connected. HTTP request sent, awaiting response... 200 OK Length: 3579275 (3.4M) [text/plain] Saving to: `pear/install-pear-nozlib.phar' 100%[=========================================================================================================================>] 3,579,275 341K/s in 9.7s 2016-03-02 11:43:47 (360 KB/s) - `pear/install-pear-nozlib.phar' saved [3579275/3579275] [PEAR] Archive_Tar - installed: 1.4.0 [PEAR] Console_Getopt - installed: 1.4.1 [PEAR] Structures_Graph- installed: 1.1.1 [PEAR] XML_Util - installed: 1.3.0 [PEAR] PEAR - installed: 1.10.1 Wrote PEAR system config file at: /usr/local/php7/etc/pear.conf You may want to add: /usr/local/php7/lib/php to your php.ini include_path /root/Downloads/php-src-php-7.0.3/build/shtool install -c ext/phar/phar.phar /usr/local/php7/bin ln -s -f phar.phar /usr/local/php7/bin/phar Installing PDO headers: /usr/local/php7/include/php/ext/pdo/
添加软链接
ln -s /usr/local/php7/bin/php /usr/bin/php7 ln -s /usr/local/php7/bin/php-config /usr/bin/php7-config ln -s /usr/local/php7/bin/phpize /usr/bin/php7ize ln -s /usr/local/php7/sbin/php-fpm /usr/sbin/php7-fpm
别忘记了拷贝配置文件
cp php.ini-production /usr/local/php7/etc/php.ini cp sapi/fpm/init.d.php-fpm /etc/init.d/php7-fpm chmod +x /etc/init.d/php7-fpm cp /usr/local/php7/etc/php-fpm.conf.default /usr/local/php7/etc/php-fpm.conf cp /usr/local/php7/etc/php-fpm.d/www.conf.default /usr/local/php7/etc/php-fpm.d/www.conf
初探扩展
本篇笔记紧紧是自己的学习过程,更规范的文档,大家可以参考鸟哥的博客 http://www.laruence.com/2009/04/28/719.html
通过框架自动生成一个最简单的扩展,到源代码目录的ext
目录下
[root@localhost ext]# ./ext_skel --extname=helloworld
执行完之后会出现如下文档,后面依照下面说的8步来操作。
Creating directory helloworld Creating basic files: config.m4 config.w32 .gitignore helloworld.c php_helloworld.h CREDITS EXPERIMENTAL tests/001.phpt helloworld.php [done]. To use your new extension, you will have to execute the following steps: 1. $ cd .. 2. $ vi ext/helloworld/config.m4 3. $ ./buildconf 4. $ ./configure --[with|enable]-helloworld 5. $ make 6. $ ./sapi/cli/php -f ext/helloworld/helloworld.php 7. $ vi ext/helloworld/helloworld.c 8. $ make Repeat steps 3-6 until you are satisfied with ext/helloworld/config.m4 and step 6 confirms that your module is compiled into PHP. Then, start writing code and repeat the last two steps as often as necessary.
在执行上面的8步操作之前,我们先看下生成了哪些文件
-rw-r--r-- 1 zhoumengkang staff 11 2 27 18:40 CREDITS -rw-r--r-- 1 zhoumengkang staff 0 2 27 18:40 EXPERIMENTAL -rw-r--r-- 1 zhoumengkang staff 2216 2 27 18:40 config.m4 -rw-r--r-- 1 zhoumengkang staff 383 2 27 18:40 config.w32 -rw-r--r-- 1 zhoumengkang staff 5325 2 27 18:40 helloworld.c -rw-r--r-- 1 zhoumengkang staff 514 2 27 18:40 helloworld.php -rw-r--r-- 1 zhoumengkang staff 2381 2 27 18:40 php_helloworld.h drwxr-xr-x 3 zhoumengkang staff 102 2 27 18:40 tests
理解各个文件的作用
CREDITS
文件用纯文本格式列出了扩展的贡献者和(或 )维护者。此文件的主要用途是为已捆绑的扩展生成被 phpcredits() 所使用的荣誉信息。按惯例,文件的第一行应保存扩展的名称,第二行是用逗号分隔的贡献者名单。
EXPERIMENTAL
具体用途不清楚,应该是和实验相关的内容吧。
config.m4
UNIX 构建系统配置文件,指导phpize
命令生成./configure
脚本。(重点)
config.w32
Windows 构建系统配置文件。(Unix 环境上可以忽略)
helloworld.c
扩展程序
php_helloworld.h
扩展的头文件
helloworld.php
用于扩展编译安装之后的测试用,下面会执行使用
tests
测试脚本可以放到该目录下,后面也会使用到。
下面开始操作
[root@localhost ext]# cd .. [root@localhost php-7.0.2]# vim ext/helloworld/config.m4
没有依赖别的库,把这几行注释去掉
依照文档说的执行./buildconf
提示
[root@localhost php-7.0.2]# ./buildconf You should not run buildconf in a release package. use buildconf --force to override this check.
其实phpize
就是对buildconf
的封装,我们使用phpize
命令
[root@localhost php-7.0.2]# cd ext/helloworld/ [root@localhost helloworld]# phpize Configuring for: PHP Api Version: 20151012 Zend Module Api No: 20151012 Zend Extension Api No: 320151012 [root@localhost helloworld]# ./configure --with-php-config=/usr/local/php7/bin/php-config ... [root@localhost helloworld]# make [root@localhost helloworld]# cd .. [root@localhost ext]# cd .. [root@localhost php-7.0.2]# ./sapi/cli/php -f ext/helloworld/helloworld.php Functions available in the test extension: confirm_helloworld_compiled Congratulations! You have successfully modified ext/helloworld/config.m4. Module helloworld is now compiled into PHP.
至此完成了文档的前6步,也就是确保了扩展已经编译到PHP中了。可以看下ext/helloworld/helloworld.php
代码就明白其检测原理了。
下面通过make install
把so文件拷贝扩展目录
root@localhost php-7.0.2]# cd ext/helloworld/ [root@localhost helloworld]# make install ... Installing shared extensions: /usr/local/php7/lib/php/extensions/no-debug-zts-20151012/
如果后面需要使用该扩展就可以在php.ini
中添加该扩展并使用了。
关于tests
目录里面的测试文件,可以通过make test
来测试,在下面一篇中,我们将自己编写下测试文件。
通过原型来生成扩展框架
[root@localhost ext]# mkdir zmk_test [root@localhost ext]# cd zmk_test/ [root@localhost zmk_test]# vim test.proto
int zmk_test_inc(int a)
然后通过就能生成扩展了
[root@localhost ext]# ./ext_skel --extname=zmktest1 --proto=./zmk_test/test.proto ... [root@localhost ext]# cd zmktest1/
然后vim zmktest1.c
,发现我们原型设定的函数已经给我们生成好了,参数名都一样。
PHP_FUNCTION(zmk_test_inc) { int argc = ZEND_NUM_ARGS(); zend_long a; if (zend_parse_parameters(argc TSRMLS_CC, "l", &a) == FAILURE) return; php_error(E_WARNING, "zmk_test_inc: not yet implemented"); }
稍微修改下
PHP_FUNCTION(zmk_test_inc) { int argc = ZEND_NUM_ARGS(); zend_long a; if (zend_parse_parameters(argc TSRMLS_CC, "l", &a) == FAILURE) return; //php_error(E_WARNING, "zmk_test_inc: not yet implemented"); RETURN_LONG(a+1); }
然后就是和上面的扩展开发测试的一样,修改config.m4
->phpize
->make
这里我学习编写下单元测试,进入到tests
目录,添加一个测试文件002.phpt
,测试上面新增的方法,zmk_test_inc(20)
期望值当然是21啦
--TEST-- Check for function zmk_test_inc --SKIPIF-- <?php if (!extension_loaded("zmktest")) print "skip"; ?> --FILE-- <?php echo zmk_test_inc(20); ?> --EXPECT-- 21
然后make test
,都通过啦
[root@localhost zmktest1]# make test Build complete. Don't forget to run 'make test'. ===================================================================== PHP : /usr/local/php7/bin/php PHP_SAPI : cli PHP_VERSION : 7.0.2 ZEND_VERSION: 3.0.0 PHP_OS : Linux - Linux localhost.localdomain 2.6.32-431.el6.x86_64 #1 SMP Fri Nov 22 03:15:09 UTC 2013 x86_64 INI actual : /root/lnmp1.2-full/php-7.0.2/ext/zmktest1/tmp-php.ini More .INIs : CWD : /root/lnmp1.2-full/php-7.0.2/ext/zmktest1 Extra dirs : VALGRIND : Not used ===================================================================== TIME START 2016-01-27 16:54:42 ===================================================================== PASS Check for zmktest1 presence [tests/001.phpt] PASS Check for function zmk_test_set [tests/002.phpt] ===================================================================== TIME END 2016-01-27 16:54:42 ===================================================================== TEST RESULT SUMMARY --------------------------------------------------------------------- Exts skipped : 0 Exts tested : 44 --------------------------------------------------------------------- Number of tests : 2 2 Tests skipped : 0 ( 0.0%) -------- Tests warned : 0 ( 0.0%) ( 0.0%) Tests failed : 0 ( 0.0%) ( 0.0%) Expected fail : 0 ( 0.0%) ( 0.0%) Tests passed : 2 (100.0%) (100.0%) --------------------------------------------------------------------- Time taken : 0 seconds ===================================================================== This report can be automatically sent to the PHP QA team at http://qa.php.net/reports and http://news.php.net/php.qa.reports This gives us a better understanding of PHP's behavior. If you don't want to send the report immediately you can choose option "s" to save it. You can then email it to qa-reports@lists.php.net later. Do you want to send this report now? [Yns]: n [root@localhost zmktest1]# make install Installing shared extensions: /usr/local/php7/lib/php/extensions/no-debug-zts-20151012/
关于报错检出
在我们自己的扩展目录里会有一个和扩展同名的php文件,也可以在那里面测试,make test
不会报错,但是在php文件里运行就会有比较多的报错信息了。
php -d"extension=zmktest1.so" zmktest1.php
同时也可以在编译的时候加上--enable-debug
试试内存分配
PHP_FUNCTION(self_concat) { char *str = NULL; int argc = ZEND_NUM_ARGS(); size_t str_len; zend_long n; char *result; char *ptr; int result_length; if (zend_parse_parameters(argc TSRMLS_CC, "sl", &str, &str_len, &n) == FAILURE) return; result_length = (str_len * n); result = (char *) emalloc(result_length +1); ptr = result; while(n--){ memcpy(ptr,str,str_len); ptr += str_len; } *ptr = '\0'; RETURN_STRINGL(result,result_length); }
扩展依赖
http://www.laruence.com/2009/08/18/1042.html
zend_module_entry
结构体中的_zend_module_dep
即为依赖的包
struct _zend_module_entry { unsigned short size; unsigned int zend_api; unsigned char zend_debug; unsigned char zts; const struct _zend_ini_entry *ini_entry; const struct _zend_module_dep *deps; const char *name; const struct _zend_function_entry *functions; int (*module_startup_func)(INIT_FUNC_ARGS); int (*module_shutdown_func)(SHUTDOWN_FUNC_ARGS); int (*request_startup_func)(INIT_FUNC_ARGS); int (*request_shutdown_func)(SHUTDOWN_FUNC_ARGS); void (*info_func)(ZEND_MODULE_INFO_FUNC_ARGS); const char *version; size_t globals_size; #ifdef ZTS ts_rsrc_id* globals_id_ptr; #else void* globals_ptr; #endif void (*globals_ctor)(void *global); void (*globals_dtor)(void *global); int (*post_deactivate_func)(void); int module_started; unsigned char type; void *handle; int module_number; const char *build_id; };
替换相关宏,比如下面我的扩展依赖standard
扩展包
/* {{{ zmk_test_functions[] * * Every user visible function must have an entry in zmk_test_functions[]. */ const zend_function_entry zmk_test_functions[] = { PHP_FE(confirm_zmk_test_compiled, NULL) /* For testing, remove later. */ PHP_FE(self_concat, NULL) PHP_FE(local_upload, NULL) PHP_FE_END /* Must be the last line in zmk_test_functions[] */ }; /* }}} */ static const zend_module_dep zmk_test_deps[] = { ZEND_MOD_REQUIRED("standard") ZEND_MOD_END }; /* {{{ zmk_test_module_entry */ zend_module_entry zmk_test_module_entry = { STANDARD_MODULE_HEADER_EX, NULL, zmk_test_deps, "zmk_test", zmk_test_functions, PHP_MINIT(zmk_test), PHP_MSHUTDOWN(zmk_test), PHP_RINIT(zmk_test), /* Replace with NULL if there's nothing to do at request start */ PHP_RSHUTDOWN(zmk_test), /* Replace with NULL if there's nothing to do at request end */ PHP_MINFO(zmk_test), PHP_ZMK_TEST_VERSION, STANDARD_MODULE_PROPERTIES }; /* }}} */
关于其中的依赖的意思是说,如果A依赖B,那么在A中可以使用B中定义好了的PHP_FUNCTION
吗?还是别的
我尝试把self_concat
的返回值写成,也就说调用了standard
里的字符串函数explode
RETURN_ARR(explode("",arg));
重新编译
[root@localhost zmk_test]# phpize ... [root@localhost zmk_test]# ./configure --with-php-config=/usr/local/php7/bin/php-config ... [root@localhost zmk_test]# make clean ... [root@localhost zmk_test]# make ... [root@localhost zmk_test]# make install
但是运行提示(扩展名我定义为了zmk_test
)
[root@localhost zmk_test]# php -d"extension=zmk_test.so" -r "echo self_concat('aaa');" php: symbol lookup error: /usr/local/php7/lib/php/extensions/no-debug-zts-20151012/zmk_test.so: undefined symbol: explode