## 扩展开发前言
扩展作用:重新定义PHP行为对PHP进行HACK,提供内部函数或内部类,提升执行性能等
---
### PHP扩展分类
PHP中的扩展分为两类:PHP扩展、Zend扩展,对内核而言这两个分别称之为:模块(module)、扩展(extension),我们主要介绍是PHP扩展,也就是模块.
#### 加载区别:
* PHP扩展(又名PHP“模块”)使用“extension = test.so”行加载到INI文件中
* Zend扩展使用“zend_extension = test.so”行加载到INI文件中
Zend扩展比PHP扩展更复杂,因为它们有更多的钩子,而且更接近Zend引擎及其虚拟机(整个PHP源代码中最复杂的部分).
Zend扩展例子如:OPCache,XDebug,phpdbg,Zend扩展通常用来处理两种任务:调试器和剖析器.如果您的目标是“只是”向PHP 添加一些新概念(函数,类,常量等),那么您将使用PHP扩展,但如果需要更改PHP的当前行为,可能Zend扩展将会更好.
## PHP扩展生命周期
![PHP扩展生命周期](https://box.kancloud.cn/7d032a8d3fb47f12b358da564ad9c58f_550x712.png)
```code
/* Zend扩展结构 -- zend_extension.h */
struct _zend_extension {
... /* 扩展基础信息 */
startup_func_t startup; // STARTUP() */
shutdown_func_t shutdown; // SHUTDOWN() 模块关闭 */
activate_func_t activate; // ACTIVE() 请求启动 */ */
deactivate_func_t deactivate; // DEACTIVATE() 请求关闭 */
message_handler_func_t message_handler; // MESSAGE_HANDLER() 在扩展注册后调用 */
op_array_handler_func_t op_array_handler; //在脚本编译后(zend compilation)后调用的钩子函数 */
statement_handler_func_t statement_handler; /* */
fcall_begin_handler_func_t fcall_begin_handler; /* 在处理opcode时调用 */
fcall_end_handler_func_t fcall_end_handler; /* */
op_array_ctor_func_t op_array_ctor; /* 构造OPArray时调用 */
op_array_dtor_func_t op_array_dtor; /* 销毁OPArray时调用 */
int (*api_no_check)(int api_no); /* API_NO_CHECK() 用来检测扩展是否兼容 */
int (*build_id_check)(const char* build_id); /* BUILD_ID_CHECK() */
...
DL_HANDLE handle; /* dlopen()返回句柄 */
int resource_number; /* 用于管理该扩展名的内部编号 */
};
/* php扩展(模块)结构 -- zend_modules.h */
struct _zend_module_entry {
...
int (*module_startup_func)(INIT_FUNC_ARGS); /* MINIT() 模块初始化回调函数,通过PHP_MINIT_FUNCTION()或ZEND_MINIT_FUNCTION()宏完成定义 */
int (*module_shutdown_func)(SHUTDOWN_FUNC_ARGS); /* MSHUTDOWN() 模块关闭阶段回调的函数.,通过PHP_MSHUTDOWN_FUNCTION()或ZEND_MSHUTDOWN_FUNCTION()定义, */
int (*request_startup_func)(INIT_FUNC_ARGS); /* RINIT() 请求开始前回调函数,通过PHP_RINIT_FUNCTION()或ZEND_RINIT_FUNCTION()宏定义. */
int (*request_shutdown_func)(SHUTDOWN_FUNC_ARGS); /* RSHUTDOWN() 请求结束时回调函数,通过PHP_RSHUTDOWN_FUNCTION()或ZEND_RSHUTDOWN_FUNCTION()宏定义 */
void (*info_func)(ZEND_MODULE_INFO_FUNC_ARGS); /* PHPINFO() php_info展示的扩展信息处理函数,调用phpinfo()时触发此函数 */
...
void (*globals_ctor)(void *global); /* GINIT() This funtion is called to initialize a module's globals before any module_startup_func. */
void (*globals_dtor)(void *global); /* GSHUTDOWN() This funtion is called to deallocate a module's globals after any module_shutdown_func. */
int (*post_deactivate_func)(void); /* PRSHUTDOWN() 晚于RSHUTDOWN调用, post-RSHUTDOWN function */
...
};
```
module_startup/module_shutdown:可用来注册/销毁类,全局变量,INI配置,常量
request_startup/request_shutdown:可用来注册/销毁特定于每个请求的变量
混合扩展可以注册为zend扩展或php模块后,在启动函数(startup或minit)中再注册另一个结构.
```c
#include "php.h"
#include "Zend/zend_extensions.h"
#include "php_pib.h"
#define PRINT(what) fprintf(stderr, what "\n");
/* Declared as static, thus private */
static zend_module_entry pib_module_entry = {
STANDARD_MODULE_HEADER,
"pib",
NULL, /* Function entries */
PHP_MINIT(pib), /* Module init */
PHP_MSHUTDOWN(pib), /* Module shutdown */
PHP_RINIT(pib), /* Request init */
PHP_RSHUTDOWN(pib), /* Request shutdown */
NULL, /* Module information */
"0.1", /* Replace with version number for your extension */
STANDARD_MODULE_PROPERTIES
};
/* This line should stay commented
ZEND_GET_MODULE(pib)
*/
zend_extension_version_info extension_version_info = {
ZEND_EXTENSION_API_NO,
ZEND_EXTENSION_BUILD_ID
};
zend_extension zend_extension_entry = {
"pib-zend-extension",
"1.0",
"PHPInternalsBook Authors",
"http://www.phpinternalsbook.com",
"Our Copyright",
pib_zend_extension_startup,
pib_zend_extension_shutdown,
pib_zend_extension_activate,
pib_zend_extension_deactivate,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
STANDARD_ZEND_EXTENSION_PROPERTIES
};
static void pib_zend_extension_activate(void)
{
PRINT("Zend extension new request starting up");
}
static void pib_zend_extension_deactivate(void)
{
PRINT("Zend extension current request is shutting down");
}
static int pib_zend_extension_startup(zend_extension *ext)
{
PRINT("Zend extension is starting up");
/* When the Zend extension part will startup(), make it register
a PHP extension by calling ourselves zend_startup_module() */
return zend_startup_module(&pib_module_entry);
}
static void pib_zend_extension_shutdown(zend_extension *ext)
{
PRINT("Zend extension is shutting down");
}
static PHP_MINIT_FUNCTION(pib)
{
PRINT("PHP extension is starting up");
return SUCCESS;
}
static PHP_MSHUTDOWN_FUNCTION(pib)
{
PRINT("PHP extension is shutting down");
return SUCCESS;
}
static PHP_RINIT_FUNCTION(pib)
{
PRINT("PHP extension new request starting up");
return SUCCESS;
}
static PHP_RSHUTDOWN_FUNCTION(pib)
{
PRINT("PHP extension current request is shutting down");
return SUCCESS;
}
```
#### zend引擎可修改的全局变量函数指针
```c
/* AST, Zend/zend_ast.h: */
void (*zend_ast_process_t)(zend_ast *ast)
/* Compiler, Zend/zend_compile.h: */
zend_op_array *(*zend_compile_file)(zend_file_handle *file_handle, int type)
zend_op_array *(*zend_compile_string)(zval *source_string, char *filename)
/* Executor, Zend/zend_execute.h: */
void (*zend_execute_ex)(zend_execute_data *execute_data)
void (*zend_execute_internal)(zend_execute_data *execute_data, zval *return_value)
/* GC, Zend/zend_gc.h: */
int (*gc_collect_cycles)(void)
/* TSRM, TSRM/TSRM.h: */
void (*tsrm_thread_begin_func_t)(THREAD_T thread_id)
void (*tsrm_thread_end_func_t)(THREAD_T thread_id)
/* Error, Zend/zend.h: */
void (*zend_error_cb)(int type, const char *error_filename, const uint error_lineno, const char *format, va_list args)
/* Exceptions, Zend/zend_exceptions.h: */
void (*zend_throw_exception_hook)(zval *ex)
/* Lifetime, Zend/zend.h: */
void (*zend_on_timeout)(int seconds)
void (*zend_interrupt_function)(zend_execute_data *execute_data)
void (*zend_ticks_function)(int ticks)
```
## 编码规则
php-src/CODING_STANDARDS
* 用来区别函数行为的数值,除了0,1外,尽量定义为常量.
* 用PHP封装好的函数(emalloc(), efree(), estrdup())分配内存.
* 变量名和函数名为小写字母加下划线组成,尽量短但不要用缩写.
* 如果是同一父集下的函数,使用parent_*加前缀的命名方法,如(file_get_contents,file_put_contents).
* 类名和类中的方法使用驼峰命名法命名,类名首字母大写,类的方法首字母小写(如 FooBar->getData() ).
* 不要使用`// ...`风格的注释,使用`/* ... */`格式的注释。
* 使用tab缩进(四个空格的空间).
* 变量声明和语句块之间留一个空行, 逻辑语句块之间也要有空行, 函数与函数之间留一到两个空行.
* 预处理语句(例如 #if)必须写在第一列,如果要缩进预处理语句也要把 # 号放在一行的开始, 紧接着是任意数量的空格
## 编译安装PHP
```shell
make clean
./configure --prefix=/home/ll/workspace/C/php-7-1-8-install --disable-all --enable-cli --enable-debug
make -j4
make install
```
#### TODO::扩展的加载,和钩子函数的调用过程
## 编写PHP扩展(模块)的步骤:
1. 通过ext目录下ext_skel脚本生成扩展的基本框架:./ext_skel --extname;
2. 修改config.m4配置:设置编译配置参数、设置扩展的源文件、依赖库/函数检查等等;
3. 编写扩展要实现的功能:按照PHP扩展的格式以及PHP提供的API编写功能;
4. 生成configure:扩展编写完成后执行phpize脚本生成configure及其它配置文件;
5. 编译&安装:./configure、make、make install,然后将扩展的.so路径添加到php.ini中.
`![PHP扩展的开发过程](https://box.kancloud.cn/275f778ae3e772530fae837ee10a46ae_990x706.jpg)`
## PHP扩展(模块)生成和编译
骨架生成器脚本位于php-src/ext/ext_skel中,其使用的模板存储在 php-src/ext/skeleton
### 基本用法
```code
> php-src/ext/ext_skel
./ext_skel --extname=module [--proto=file] [--stubs=file] [--xml[=file]]
[--skel=dir] [--full-xml] [--no-help]
--extname=module module为你的扩展名称 (module is the name of your extension)
--proto=file 从file原型文件创建一组PHP函数,方便开发基于库的扩展.(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 指定此参数会造成 ext_skel 会在生成的文件里省略很多有用的注释。do not try to be nice and create comments in the code
and helper functions to test if the module compiled
> php-src/ext/ext_skel --extname=pib
> cd php-src/ext
> tree pib/
pib/
├── config.m4 //UNIX 构建系统配置
├── config.w32 //Windows 构建系统配置
├── CREDITS //扩展描述文件,包含扩展名,开发者信息。默认生成时只带有扩展名
├── EXPERIMENTAL //实验功能说明
├── php_pib.h //包含附加的宏、原型和全局量
├── pib.c //扩展源文件
├── pib.php //测试文件
└── tests //测试脚本目录
└── 001.phpt //测试脚本。测试方法:php ../../run-tests.php ./pib/001.phpt (run-tests.php文件存在php源码根目录)
```
#### proto原型文件
原型文件有点类似 C 头文件,根据其中申明的函数,生成函数骨架代码和其他相关代码。如 pib.proto,内容为 `string pib_hello_world (string name)`
原型文件的格式,类似于 C 头文件中的函数申明的方式,返回值、函数名、形参类型、形参名。 参数用 () 包裹,多个参数以 , 分隔,函数申明末尾不需要以 ; 结尾,一行一个函数声明。
原型文件的生成依赖于 awk 脚本 ext/skeleton/create_stubs ,由其中 convert 函数可知,其支持的参数类型有
```code
int,long
bool,boolean
double,float
string
array,object,mixed
resource,handle
```
## 修改config.m4
config.m4是扩展的编译配置文件,它被include到configure.in文件中,最终被autoconf编译为configure,编写扩展时我们只需要在config.m4中修改配置即可.
首先修改 config.m4 ,去掉 PHP_ARG_ENABLE 和 --enable-pib 这两行前面的 dnl,dnl 是注释符号。修改后如下
```code
PHP_ARG_ENABLE(pib, whether to enable pib support,
dnl Make sure that the comment is aligned:
[ --enable-pib Enable pib support])
```
PHP_ARG_ENABLE函数第一参数表示扩展名,第二个和第三个在生成configure时的一些提示信息。--enable-extname表示不依赖第三方库,而--with-extname表示需要第三方库。
PHP_NEW_EXTENSION(pib, pib.c, $ext_shared,, -DZEND_ENABLE_STATIC_TSRMLS_CACHE=1)函数声明了这个扩展的名称、需要的源文件名、此扩展的编译形式。
如果是多个文件的话,就在第二个参数后加空格再填写你的源文件地址,需要换行的话记得反斜杠\。
### config.m4常用宏
PHP在acinclude.m4中基于autoconf/automake的宏封装了很多可以直接使用的宏,下面介绍几个比较常用的宏:
* `PHP_ARG_WITH(arg_name,check message,help info)`: 定义一个--with-feature[=arg]这样的编译参数,调用的是autoconf的AC_ARG_WITH,这个宏有5个参数,常用的是前三个,分别表示:参数名、执行./configure是展示信息、执行--help时展示信息,第4个参数为默认值,如果不定义默认为"no",通过这个宏定义的参数可以在config.m4中通过$PHP_参数名(大写)访问.比如:PHP_ARG_WITH(aaa, aaa-configure, help aa)后面通过$PHP_AAA就可以读取到--with-aaa=xxx设置的值了
* `PHP_ARG_ENABLE(arg_name,check message,help info)`: 定义一个--enable-feature[=arg]或--disable-feature参数,--disable-feature等价于--enable-feature=no,这个宏与PHP_ARG_WITH类似,通常情况下如果配置的参数需要额外的arg值会使用PHP_ARG_WITH,而如果不需要arg值,只用于开关配置则会使用PHP_ARG_ENABLE.
* `AC_MSG_CHECKING()/AC_MSG_RESULT()/AC_MSG_ERROR()`: ./configure时输出结果,其中error将会中断configure执行.
* `AC_DEFINE(variable, value, [description])`: 定义一个宏,比如:AC_DEFINE(IS_DEBUG, 1, []),执行autoheader时将在头文件中生成:#define IS_DEBUG 1.
* `PHP_ADD_INCLUDE(path)`: 添加include路径,即:gcc -Iinclude_dir,#include "file";将先在通过-I指定的目录下查找,扩展引用了外部库或者扩展下分了多个目录的情况下会用到这个宏.
* `PHP_CHECK_LIBRARY(library, function [, action-found [, action-not-found [, extra-libs]]])`: 检查依赖的库中是否存在需要的function,action-found为存在时执行的动作,action-not-found为不存在时执行的动作,比如扩展里使用到线程pthread,检查pthread_create(),如果没找到则终止./configure执行:
```code
PHP_ADD_INCLUDE(pthread, pthread_create, [], [
AC_MSG_ERROR([not find pthread_create() in lib pthread])
])
```
* `AC_CHECK_FUNC(function, [action-if-found], [action-if-not-found])`: 检查函数是否存在. (8)PHP_ADD_LIBRARY_WITH_PATH($LIBNAME, $XXX_DIR/$PHP_LIBDIR, XXX_SHARED_LIBADD): 添加链接库.
* `PHP_NEW_EXTENSION(extname, sources [, shared [, sapi_class [, extra-cflags [, cxx [, zend_ext]]]]])`: 注册一个扩展,添加扩展源文件,确定此扩展是动态库还是静态库,每个扩展的config.m4中都需要通过这个宏完成扩展的编译配置.
更多autoconf及PHP封装的宏大家可以在用到的时候再自行检索,同时ext目录下有大量的示例可供参考.
[参考链接](https://secure.php.net/manual/en/internals2.buildsys.configunix.php)
## 编译安装测试扩展
```shell
cd ext/pib
../../../php-7-1-8-install/bin/phpize
./configure --with-php-config=../../../php-7-1-8-install/bin/php-config
make
cd ../../sapi/cli
./php -d extension='../../ext/pib/modules/pib.so' -r "echo confirm_pib_compiled('hello world');"
```
在扩展目录(ext/pib/)中,phpize会根据config.m4生一个configure文件.
执行./configure后会生makefiles文件,再执行make . 然后扩展中modules目录下会发现一个pib.so文件 .
make install 会将扩展复制到PHP安装路径的扩展目录当中 。
测试会输出Congratulations! You have successfully modified ext/pib/config.m4. Module hello world is now compiled into PHP.
### 发布前执行
`phpize --clean`
## 参考资料:
http://www.phpinternalsbook.com/index.html
https://github.com/pangudashu/php7-internal/
https://secure.php.net/manual/en/internals2.structure.php
http://www.laruence.com/2009/04/28/719.html
https://andot.gitbooks.io/bped/c02s03.html
https://github.com/lxy254069025/php-extension-book
http://php.net/manual/zh/internals2.buildsys.configunix.php
http://www.php-internals.com/book/?p=chapt11/11-02-00-extension-hello-world