Toggle navigation
leo's blog
PHP
JavaScript
MySQL
Linux
瞎扯
PHP7扩展开发之"常量定义"
PHP
2020-07-19 11:34:52
63
简介:
在PHP使用过程中有很多的内置常量可以直接使用,当自定义扩展时,也可以通过扩展来提供系统内置的常量直接供开发者使用,本文介绍如何扩展基础数据类型的常量与数组类型的常量
#1、PHP扩展中的钩子函数 想要理解PHP扩展中的5个钩子函数那么就必须对PHP生命周期有所了解,不同的生命周期中对应着扩展中的钩子函数 ## 1.1、PHP生命周期的划分  关于PHP的生命周期可以划分为上图中的5个阶段。并不是每次都会执行完整的阶段,此处会根据SAPI(cli、fpm)的实现使用的那种模式会有部分差异。例如命令行模式下会完整的执行完成5个阶段而对于fastcgi模式则在启动时只执行一次模块初始化,然后各个请求只经历请求初始化、执行、关闭阶段, ###模块初始化 模块初始化阶段主要是进行PHP框架以及zend引擎的初始化操作,这个阶段一般只在SAPI启动时执行一次。改阶段在源码中的入口函数为php_module_startup() ###请求初始化阶段 该阶段是在请求处理前每一个请求都会经历的一个阶段,对于fpm是在worker进程中accept一个请求且正常读取、解析完请求数据后的一个阶段。该阶段在源码中入口函数为php_request_startup() ###执行脚本阶段 该阶段包括PHP代码的编译执行的核心阶段,也是zend引擎的最主要的一个功能,在编译阶段,PHP脚本从源代码最终生成opline指令。最终被执行器执行,该阶段在源码中的入口函数为php_execute_script() ###请求关闭阶段 在PHP脚本解析执行完毕后将进入此阶段,这个阶段主要是将flush输出,发送HTTP应答头、清理全局变量、关闭编译器、关闭执行器等操作。该阶段是与请求初始化阶段相反的操作,入口函数为php_request_shutdown() ###模块关闭阶段 该阶段在SAPI关闭时执行,与模块初始化阶段对应,主要用于PHP各个模块的关闭、资源清理等操作。函数入口为php_module_shutown() ##1.2、扩展中的钩子函数 php扩展的源码中通过zend_module_entry结构来表示扩展名称、版本、函数列表以及钩子函数等。每一个自定义的扩展必须要存在一个这一的结构体,并且名称是{module_name}_module_entry。只有拥有了此结构之后,内核才能获取到扩展所提供的功能。格式其结构为: ```c typedef struct _zend_module_entry zend_module_entry; 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); //PHPinfo展示的信息处理 void (*info_func)(ZEND_MODULE_INFO_FUNC_ARGS); //...其他的结构信息省略 详细信息参考源码中的Zend/zend_module.h }; ``` 在结构体中的几个回调函数即钩子函数,后期PHP源码在php_module_startup阶段将扩展加载到PHP中。 ### 1.3、各个钩子函数的设置 在PHP源码执行到相关阶段是会调用对应阶段的各个钩子函数 #### 模块初始化 ```c //模块初始化阶段实现的功能,用于在SAPI启动后进行操作,例如内部类的注册,常量定义等 PHP_MINIT_FUNCTION(phper_leo) { return SUCCESS; } ``` #### 模块关闭 ```c //模块关闭阶段执行 PHP_MSHUTDOWN_FUNCTION(phper_leo) { return SUCCESS; } ``` ####请求初始化 ```c //请求初始化阶段执行,例如请求过滤等操作 PHP_RINIT_FUNCTION(phper_leo) { #if defined(COMPILE_DL_PHPER_LEO) && defined(ZTS) ZEND_TSRMLS_CACHE_UPDATE(); #endif return SUCCESS; } ``` ####请求结束 ```c //请求结束阶段的钩子 PHP_RSHUTDOWN_FUNCTION(phper_leo) { return SUCCESS; } ``` ####phpinfo查看信息回调 ```c //phpinfo查看信息时执行 PHP_MINFO_FUNCTION(phper_leo) { php_info_print_table_start(); php_info_print_table_header(2, "phper_leo support", "enabled"); php_info_print_table_end(); } ``` **定义常量需要在PHP代码中任何位置都可以使用所以需要在模块初始化阶段就完成常量的定义,即在PHP_MINIT_FUNCTION中实现** #2、常规类型的常量定义 在PHP中提供了很多不同类型注册常量使用的宏,可以通过使用这些宏在扩展中快速定义常量。 ##2.1、使用格式 ```c //注册bool常量 #define REGISTER_BOOL_CONSTANT(name,val,flags)或者 zend_register_bool_constant(name,sizeof(name)-1,val,flags) //注册整型常量 #define REGISTER_LONG_CONSTANT(name,val,flags)或者 zend_register_long_constant(name,sizeof(name)-1,val,flags) //注册浮点常量 #define REGISTER_DOUBLE_CONSTANT(name,val,flags)或者 zend_register_double_constant(name,sizeof(name)-1,val,flags) //注册字符串常量 str类型为char * #define REGISTER_STRING_CONSTANT(name,str,flags)或者 zend_register_string_constant(name,sizeof(name)-1,str,flags) ``` 除了上面使用,还可以使用REGISTER_NS_XXX系列的宏注册带namespace的常量 ##2.2、使用示例 1、设置字符串常量 直接修改扩展源码 ```c PHP_MINIT_FUNCTION(phper_leo) { // 注册字符串常量 REGISTER_STRING_CONSTANT("PHPER_LEO","define string constant value is phper_leo",CONST_CS|CONST_PERSISTENT); //CONST_CS表示区分大小写 CONST_PERSISTENT 表示常驻内存 } ``` 2、重新编译 ```shell make && make install ``` 3、创建测试文件 ```php <?php echo PHPER_LEO; //输出define string constant value is phper_leo ``` 对于上面使用内置的宏或者函数可以注册基本类型的常量,但是如果需要注册数组类型的常量就不能实现,因此可以使用 zend_register_constant()来进行注册。 #3、数组类型的常量定义 1、zend_register_constant原型 ```c ZEND_API int zend_register_constant(zend_constant *c) ``` 2、zend_constant结构 ```c typedef struct _zend_constant { zval value;//zval结构数据 zend_string *name;//名称 int flags;//标识 例如CONST_CS|CONST_PERSISTENT int module_number;//模块ID } zend_constant; ``` 3、模块初始化阶段设置常量 ```c PHP_MINIT_FUNCTION(phper_leo) { // 注册字符串常量 REGISTER_STRING_CONSTANT("PHPER_LEO","define string constant value is phper_leo",CONST_CS|CONST_PERSISTENT); // 注册数组类型常量 zend_constant arr; zval value;//常量数组的值 //创建数组 ZVAL_NEW_PERSISTENT_ARR(&arr.value);// // 初始化数组 zend_hash_init(Z_ARRVAL(arr.value),0,NULL,NULL,1); //数组加入元素 zend_string *key = zend_string_init("site", 4, 1); ZVAL_STR(&value, zend_string_init("https://phpclub.org.cn", 22, 1)); zend_hash_update(Z_ARRVAL(arr.value),key,&value); //常量名称 zend_string_init创建字符串 arr.name = zend_string_init("ARR_CONSTANT",12,1); arr.flags = CONST_CS|CONST_PERSISTENT; // module_number是回调函数extension_startup传给我们的模块唯一标识,从而说明这个常量是我们的扩展注册的 arr.module_number = module_number; //注册数组常量 zend_register_constant(&arr); // return SUCCESS; } ``` 4、设置模块关闭常量销毁 因为在程序执行完毕,内部zval释放的时候,会进行类型检测。如果发现是array object或者resources,则会报错 ```c //模块关闭阶段执行 PHP_MSHUTDOWN_FUNCTION(phper_leo) { zval *val; val = zend_get_constant_str("ARR_CONSTANT", 12); // 销毁定义的常量数组 zend_hash_destroy(Z_ARRVAL_P(val)); ZVAL_NULL(val); return SUCCESS; } ``` 重新编译后重启php-fpm 5、创建测试文件 ```php <?php echo PHPER_LEO; echo '<hr/>'; var_dump(get_total(1,2)); echo '<hr/>'; var_dump(ARR_CONSTANT); ?> ``` 执行查看效果发现常量可以正常使用 #4、扩展中常量的使用 ```c //通过zend_string的常量名称获取常量 ZEND_API_ zval *zend_get_constant(zend_string *name) //通过char常量名称获取常量 ZEND_API zval *zend_get_constant_str(const char *name,size_t name_len); ``` 具体使用可以查看上面例子中在模块关闭阶段执行的销毁操作 *推荐使用基础的整型/字符串来定义常量**
Top