超低价nisp报名(二级比官网少一千多),可加本人v:zjhululu
1. php拓展相关介绍
php拓展为PHP提供一些扩展的功能
常见的php拓展有redis,mysql,xdebug等等
可以通过phpinfo()来查看php安装的拓展
或者利用php -m
来查看安装的插件
php拓展文件一般都放在*\php\php7.3.4nts\ext\*文件夹中
2. php程序拓展安装举例
进入拓展下载网址PECL :: The PHP Extension Community Library,搜索需要的拓展(这里以taint为例),选择与自己php版本匹配的文件下载
解压,复制php_taint.dll文件到*\php\php7.3.4nts\ext\*文件夹中,并在php.ini文件中添加extension=php_taint
重启网站(apache),如果phpinfo中显示有taint的拓展,则表示成功
3. php拓展结构
3.1. PHP拓展与Zend拓展的区别
php拓展:在/php-src/Zend/zend_modules.h头文件中定义了_zend_module_entry结构体,这个结构体就是PHP扩展的结构体,称为module,除了一些基本信息外,主要提供了以上个钩子函数。其变量定义一般为:struct {module_name}_module_entry
Zend拓展:在/php-src/Zend/zend_extensions.h头文件中定义了_zend_extension结构体,这个结构体就是Zend扩展的结构体,称为extension。相比PHP扩展,主要提供了更底层的钩子函数。其变量定义一般为:struct {extension_name}_extension
3.2. php拓展代码结构
PHP中扩展通过zend_module_entry这个结构来表示。
此结构定义了扩展的全部信息:扩展名、扩展版本、扩展提供的函数列表以及PHP四个执行阶段的hook函数等,每一个扩展都需要定义一个此结构的变量,而且这个变量的名称格式必须是:{module_name}_module_entry,内核正是通过这个结构获取到扩展提供的功能的。
struct _zend_module_entry {
unsigned short size; /*
unsigned int zend_api; * STANDARD_MODULE_HEADER
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;
// 下面为四个钩子函数
// MINT钩子函数,模块初始化时被调用
int (*module_startup_func)(INIT_FUNC_ARGS);
// RINT钩子函数,每一次请求时被调用
int (*request_startup_func)(INIT_FUNC_ARGS);
// RSHUTDOWN钩子函数,每一次请求结束后被调用
int (*request_shutdown_func)(SHUTDOWN_FUNC_ARGS);
// 调用phpinfo()时打印扩展信息
void (*info_func)(ZEND_MODULE_INFO_FUNC_ARGS);
// MSHUTDOWN钩子函数,模块结束化时被调用
int (*module_shutdown_func)(SHUTDOWN_FUNC_ARGS);
const char *version; /* 模块版本 */
size_t globals_size; /*
#ifdef ZTS *
ts_rsrc_id* globals_id_ptr; *
#else * Globals management
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; /* dlopen() 返回句柄 */
int module_number; /* 模块号 */
const char *build_id; /* 构建编号, STANDARD_MODULE_PROPERTIES_EX 的一部分*/
};
3.3. php文件目录结构
(不能用phpstudy等自带的php程序,必须去下载php源码)
php的拓展一般在/php-版本号/ext/*
文件夹下,编译后一般为dll(windows)或os(linux)文件,拓展源码主要以以下文件组成:
- text : 单元测试目录
- config.m4 :扩展的编译配置文件(Unix系统)
- config.w32 :扩展的编译配置文件(Windows系统)
- php_{$module}.h :扩展的头文件
- {$module}.c :扩展源文件
- {$module}.php :扩展的测试文件
(1)在编译扩展成功后,会在扩展目录下生成一个run-test.php
脚本文件,这个脚本会自动执行tests
目录下的所有单元测试。
(2)此外在扩展目录下还会自动生成一个{$module}.php
扩展的测试文件,可以方便的测试扩展是否可以正常加载和使用。
(3)扩展下载后只有源码,需要进行编译生成.so
扩展文件后才可以被PHP使用,config.m4
和config.w32
这两个文件就是在后续执行phpize阶段会使用到的编译配置文件。
在m4文件中,最前面的dnl宏会删除本行多余的字符,可以简单的理解为注释,如果需要编译其中一个扩展,则需要把这个拓展最前面的dnl宏都去掉。
(4)一般名为php_{$module}.h
的是扩展头文件,一般用于定义需要的常量和函数等
(5)般名称为{$module}.c
的是扩展源文件,主要由以下部分组成
- zend_module_entry :定义扩展的结构体
- PHP_FUNCTION :定义扩展的函数
- Hook_FUNCTION :如 PHP_MINIT_FUNCTION 等
4. php拓展的生命周期
PHP拓展的调用顺序与钩子函数的调用,前面我们已经得知,php拓展主要有四个钩子函数:
MINT
:模块初始化时被调用MSHUTDOWN
:模块结束化时被调用RINT
:每一次请求时被调用RSHUTDOWN
:每一次请求结束后被调用
详细步骤为:
- 如果运行的是CLI或者FPM,它将运行C
main()
;如果作为模块运行到网络服务器,像使用apxs2 SAPI(Apache2)
,则PHP在Apache启动后不久启动,并开始运行其模块的启动序列, PHP就是其中之一。在内部称启动为模块启动步骤。我们也将其缩写为MINIT步骤。 - 一旦启动,PHP将等待处理一个或几个请求。当我们谈论PHP CLI时,将只有一个请求:当前脚本要运行。但是,当我们谈论Web环境时一应该是PHP-FPM或Web服务器模块一PHP可以一个接一个地做处理多个请求。这完全依赖于你如何配置你的Wb服务器:你可以告诉它处理无限数量的请求,或在关闭并回收该过程之前处理特定数量的请求。每次一个新的请求在线程中要处理时,PHP就会运行请求启动步骤。我们称之为RINIT。
- 请求得到处理,(可能)生成了一些内容,OK。是时候关闭请求,并准备好处理另一个请求。关闭请求调用请求关闭步骤。我们称之为RSHUTDOWN。
- 当处理完X个请求(一个,几十个,数千个等),PHP最后会自行关闭,然后结束。关闭PHP进程称为模块关闭步骤。缩写为MSHUTDOWN。
- 另外,还有四个较为详细的钩子:
-
- Post请求终止:PRSHUTDOWN()
- 全局初始化:GINIT()
- 全局终止:GSHUTDOWN()
- 信息收集:MINFO()
- 则php拓展的生命周期可表示为:
-
5. php拓展开发
5.1. windows
5.1.1. php下载
windows.php.net - /downloads/releases/archives/
我下载的这两个:(7.0.9)(试了好多好多,就这个感觉可以,呜呜呜)
5.1.2. 下载Cygwin
(下载地址:Cygwin)
5.1.3. 简单配置
可以将*\php-7.0.9-nts-Win32-VC11-x86
添加到环境变量中
打开php源码中的*\php-7.0.9-src\ext\ext_skel_win32.php
进行编辑
将$cygwin_path = 'c:\cygwin\bin';
中的位置改为自己cygwin的位置
下载vc++(根据自己php版本不同下载相对应的版本,我7.0.9下载的vc++14(2015))
5.1.4. 生成拓展
进入*\php-7.0.9-src\ext\
文件夹内,运行php ext_skel_win32.php --extname=test
,这里test代表扩展名,即可生成test拓展
主要结构:
- text : 单元测试目录
- config.m4 :扩展的编译配置文件(Unix系统)
- config.w32 :扩展的编译配置文件(Windows系统)
- php_{$module}.h :扩展的头文件
- {$module}.c :扩展源文件
- {$module}.php :扩展的测试文件
5.1.5. visual studio打开,配置并生成文件
(文件更换位置了,图片中路径可能不太一样)
打开VS选择“文件”--“新建”--“从现有代码创建目录”、
选择C++
选择php扩展文件夹路径D:\phpstudy_pro\Extensions\php\php-7.0.32-src\ext\test,并且给项目命名 test
选择“使用 visual studio”,项目类型选择“动态链接库(DLL)项目”,后面一直默认下一步一直到完成。
接下来,把项目解决方案配置改为Release
右键项目,属性,添加下列内容:
内容一:
C/C++,常规,在附加包含目录后面添加下面内容(以自己的为准)
D:\phpstudy_pro\Extensions\php\php-7.0.9-src;D:\phpstudy_pro\Extensions\php\php-7.0.9-src\main;D:\phpstudy_pro\Extensions\php\php-7.0.9-src\TSRM;D:\phpstudy_pro\Extensions\php\php-7.0.9-src\Zend
内容二:
C/C++,预处理器,预处理器定义,编辑,加入以下变量:
ZEND_DEBUG=0;PHP_EXTENSION;PHP_WIN32;ZEND_WIN32;HAVE_TEST=1;COMPILE_DL_TEST;ZTS;
(这里红色部分,要改成你的扩展名称,不改成你的扩展名,php会不识别)
(最后一个变量ZTS加上是开启线程安全(TS版本),不加是关闭线程安全(NTS版本),因为我下载的编译后php是NTS,所以我没有加)
内容三:
连接器,输入,附加依赖项,编辑,将php7.lib的路径放进去(这个文件在php编译后的程序文件夹里,根目录的dev文件夹里)D:\phpstudy_pro\Extensions\php\php-7.0.9-nts-Win32-VC14-x86\dev\php7.lib
右键项目,生成
遇到问题:
问题一:有很多源文件无法打开
解决方法:随便找一个源文件搜索在自己电脑上的位置,像我的assert.h文件的目录为:
C:\Program Files (x86)\Windows Kits\10\Include\10.0.17763.0\ucrt
然后,右键项目,属性,vc++目录,在包含目录中添加以下内容(以自己目录为准):
C:\Program Files (x86)\Windows Kits\10\Include\10.0.17763.0\ucrt;C:\Program Files (x86)\Windows Kits\10\Include\10.0.17763.0\um;C:\Program Files (x86)\Windows Kits\10\Include\10.0.17763.0\shared;C:\Program Files (x86)\Windows Kits\10\Include\10.0.17763.0\winrt
如果还有其他源文件无法打开也同理
右键项目,重新生成
问题二:找不到Windows SDK版本B.1。请安装所需的版本的Windows SDK或者在项目属性页中或通过右键单击解决方案并选择”重定解决方案目标”来更改SDK版本。
解决方法:上面“项目”,“重定解决方案目标”,点击确定即可
右键项目,重新生成
问题三:无法打开包括文件:"main/config.w32.h":No such file or directory
在D:\phpstudy_pro\Extensions\php\php-7.0.9-src\win32\build\
文件夹里找到“config.w32.h.in”,将这个文件复制到D:\phpstudy_pro\Extensions\php\php-7.0.9-src\main\
文件夹里,去掉后面的“.in”
同时,为了让扩展能和php运行环境匹配,要根据自己的php运行环境的编译版本(php7.0.9是VC14编译的),在D:\phpstudy_pro\Extensions\php\php-7.0.9-src\main\文件夹里的config.w32.h文件里加上:#define PHP_COMPILER_ID "VC14"
右键项目,重新生成
问题四: LNK1120: 5 个无法解析的外部命令
就是上面最后一条忘配置了
右键项目,属性,连接器,输入,附加依赖项,编辑,将php7.lib的路径放进去(这个文件在php编译后的程序文件夹里,根目录的dev文件夹里)D:\phpstudy_pro\Extensions\php\php-7.0.9-nts-Win32-VC14-x86\dev\php7.lib
右键项目,重新生成
最后可以看到,生成成功
5.1.6. 重要文件介绍
在test_1.c文件中(我换了一个项目test_1,所以不是test了,实际以自己的项目为准)
主要有两个部分需要特别注意(检索confirm_test_1_compiled):
第一部分:这部分主要是定义一个函数,这里函数名为confirm_test_1_compiled(可以更改或在下面重新定义一个函数)
PHP_FUNCTION(confirm_test_1_compiled)
{
char *arg = NULL;
size_t arg_len, len;
zend_string *strg;
if (zend_parse_parameters(ZEND_NUM_ARGS(), "s", &arg, &arg_len) == FAILURE) {
return;
}
strg = strpprintf(0, "Congratulations! You have successfully modified ext/%.78s/config.m4. Module %.78s is now compiled into PHP.", "test_1", arg);
RETURN_STR(strg);
}
第二部分:这部分主要用于注册函数,上面定义的函数,必须在这里定义一下才能使用
const zend_function_entry test_1_functions[] = {
PHP_FE(confirm_test_1_compiled, NULL) /* For testing, remove later. */
PHP_FE_END /* Must be the last line in test_1_functions[] */
};
然后“生成”——》“生成解决方案”
5.1.7. 案例
在上面说的第一部分下面,新定义一个My_test函数,用来打印hello world
PHP_FUNCTION(My_test)
{
char *arg = NULL;
size_t arg_len, len;
zend_string *strg;
if (zend_parse_parameters(ZEND_NUM_ARGS(), "s", &arg, &arg_len) == FAILURE) {
return;
}
strg = strpprintf(0, "hello world", "test_1", arg);
RETURN_STR(strg);
}
在第二部分const zend_function_entry test_1_functions[]
里面,添加PHP_FE(My_test,NULL)
const zend_function_entry test_1_functions[] = {
PHP_FE(confirm_test_1_compiled, NULL) /* For testing, remove later. */
PHP_FE(My_test, NULL)
PHP_FE_END /* Must be the last line in test_1_functions[] */
};
然后“生成”——》“生成解决方案”
在D:\phpstudy_pro\Extensions\php\php-7.0.9-src\ext\test_1\Release
目录下生成了一个test_1.dll
文件(即拓展文件)
将其复制到D:\phpstudy_pro\Extensions\php\php-7.0.9-nts-Win32-VC14-x86\ext
文件夹中
如果D:\phpstudy_pro\Extensions\php\php-7.0.9-nts-Win32-VC14-x86
文件夹中没有php.ini文件
可以复制一下php.ini-development
文件,并将副本文件名改为php.ini
在php.ini中添加拓展extension=test_1.dll
检索; On windows:
,更改拓展文件夹位置并取消注释
接下来新建php文件:test.php
里面写入
<?php
echo My_test('111');
得到
5.2. linux下(centos)
5.2.1. 下载宝塔面板,并下载相关组件
yum install -y wget && wget -O install.sh https://download.bt.cn/install/install_6.0.sh && sh install.sh ed8484bec
5.2.2. 下载php源码
windows.php.net - /downloads/releases/archives/
我这里使用7.0.9
放到www/server/php/*下,这样可以直接在宝塔面板上进行代码的修改
5.2.3. 生成拓展源码
cd www/server/php/php-7.0.9-src/ext/
./ext_skel --extname=test //这里test代表扩展名,即生成test拓展
5.2.4. 重要文件介绍以及修改
1) config.m4
找到这几行
dnl PHP_ARG_ENABLE(test, whether to enable test support,
dnl Make sure that the comment is aligned:
dnl [ --enable-test Enable test support])
去掉这几行前面的dnl(注释作用),改为
PHP_ARG_ENABLE(test, whether to enable test support,
Make sure that the comment is aligned:
[ --enable-test Enable test support])
这样以后编译php时,./configure后面加 --enable-test 就可以加载php模块了
2)在test.c文件中
主要有两个部分需要特别注意(检索confirm_test_compiled):
第一部分:这部分主要是定义一个函数,这里函数名为confirm_test_compiled(可以更改或在下面重新定义一个函数)
PHP_FUNCTION(confirm_test_compiled)
{
char *arg = NULL;
size_t arg_len, len;
zend_string *strg;
if (zend_parse_parameters(ZEND_NUM_ARGS(), "s", &arg, &arg_len) == FAILURE) {
return;
}
strg = strpprintf(0, "Congratulations! You have successfully modified ext/%.78s/config.m4. Module %.78s is now compiled into PHP.", "test_1", arg);
RETURN_STR(strg);
}
第二部分:这部分主要用于注册函数,上面定义的函数,必须在这里定义一下才能使用
const zend_function_entry test_functions[] = {
PHP_FE(confirm_test_compiled, NULL) /* For testing, remove later. */
PHP_FE_END /* Must be the last line in test_functions[] */
};
在上面说的第一部分下面,新定义一个test_echo
函数,用来打印hello world
PHP_FUNCTION(test_echo)
{
zend_printf("hello world");
}
在第二部分const zend_function_entry test_functions[]
里面,添加PHP_FE(test_echo,NULL)
const zend_function_entry test_functions[] = {
PHP_FE(confirm_test_compiled, NULL) /* For testing, remove later. */
PHP_FE(test_echo, NULL)
PHP_FE_END /* Must be the last line in test_functions[] */
};
也可搜索:PHP_MINFO_FUNCTION(test),新增版本、作者等信息
php_info_print_table_row(2, "Version", "1.0");
php_info_print_table_row(2, "Author", "hululu");
5.2.5. 编译拓展
进入test文件夹
cd www/sever/php/php-7.0.9-src/ext/test_1/
phpize
./configure --with-php-config=/www/server/php/70/bin/php-config //后面是宝塔上面安装的php路径。检查并生成编译配置
make && make install //编译并安装
在这个目录下的modules/
里面,生成了一个test.so文件
同时,这个文件也会直接添加到 /www/server/php/70/lib/php/extensions/no-debug-non-zts-20151012/ 文件夹中(即已经编译后的php拓展文件夹中,类似于Windows中php的ext文件夹)
5.2.6. 验证拓展
先新建test.php文件,写入
<?php
test_echo();
运行php -dextension=test.so test.php 可以验证成功(打印出hello world)
修改 /www/server/php/70/etc/php.ini 文件,在里面添加 extension=test.so,保存(有时候还得重启一下。)
打开phpinfo(),可以看到拓展信息
将test.php文件放入网站目录,也可进行验证