INI设置
和上一章你看到的超级全局变量以及持久化常量一样, php.ini值必须在扩展的MINIT代码块中定义. 然而, 和其他特性不同的是, INI选项的定义仅仅由简单的启动/终止线组成.
PHP_MINIT_FUNCTION(sample4) { REGISTER_INI_ENTRIES(); return SUCCESS; } PHP_MSHUTDOWN_FUNCTION(sample4) { UNREGISTER_INI_ENTRIES(); return SUCCESS; }
定义并访问INI设置
INI指令自身是在源码文件中MINIT函数上面, 使用下面的宏完全独立的定义的, 在这两个宏之间可以定义一个或多个INI指令:
PHP_INI_BEIGN() PHP_INI_END()
这两个宏函数和ZEND_BEGIN_MODULE_GLOBALS()/ZEND_END_MODULE_GLOBALS()异曲同工. 不过这里不是typdef一个结构体, 而是对静态数据实例定义的框架组织:
static zend_ini_entry ini_entries[] = { {0,0,NULL,0,NULL,NULL,NULL,NULL,NULL,0,NULL,0,0,NULL} };
如你所见, 它定义了一个zend_ini_entry值的向量, 以一条空的记录结束. 这和你在前面看到的静态向量function_entry的定义一致.
简单的INI设置
现在, 你已经有一个INI结构体用于定义INI指令, 以及引擎注册/卸载INI设置的机制, 因此我们可以真正的去为你的扩展定义一些INI指令了. 假设你的扩展暴露了一个打招呼的函数, 就像第5章"你的第一个扩展"中一样, 不过, 你想让打招呼的话可以自定义:
PHP_FUNCTION(sample4_hello_world) { php_printf("Hello World!\n"); }
最简单最直接的方式就是定义一个INI指令, 并给它一个默认值"Hello world!":
#include "php_ini.h" PHP_INI_BEGIN() PHP_INI_ENTRY("sample4.greeting", "Hello World", PHP_INI_ALL, NULL) PHP_INI_END()
你可能已经猜到了, 这个宏的前两个参数表示INI指令的名字和它的默认值. 第三个参数用来确定引擎是否允许这个INI指令被修改(这将涉及到本章后面要介绍的访问级别问题). 最后一个参数是一个回调函数, 它将在每次INI指令的值发生变化时被调用. 你将在修改事件一节看到这个参数的细节.
译注: 如果你和译者一样遇到结果和原著结果预期不一致时, 请在测试时, 在你的MINIT()函数中增加一句"REGISTER_INI_ENTRIES();"调用, 并确保该调用在你的MINIT中分配全局空间之后执行.
现在你的INI设置已经定义, 只需要在你的打招呼函数中使用就可以了.
PHP_FUNCTION(sample4_hello_world) { const char *greeting = INI_STR("sample4.greeting"); php_printf("%s\n", greeting); }
一定要注意, char *的值是引擎所有的, 一定不要修改. 正因为这样, 所以将你本地用来临时存储INI设置值的变量定义为const修饰. 当然, 并不是所有的INI值都是字符串; 还有其他的宏用来获取整型, 浮点型以及布尔型的值:
long lval = INI_INT("sample4.intval"); double dval = INI_FLT("sample4.fltval"); zend_bool bval = INI_BOOL("sample4.boolval");
通常你想要知道的是INI设置的当前值; 不过, 作为补充, 存在几个宏可以用来读取未经修改的INI设置值:
const char *strval = INI_ORIG_STR("sample4.stringval"); long lval = INI_ORIG_INT("sample4.intval"); double dval = INI_ORIG_FLT("sample4.fltval"); zend_bool bval = INI_ORIG_BOOL("sample4.boolval");
这个例子中, INI指令的名字"sample4.greeting"增加了扩展名作为前缀, 这样来保证不会和其他扩展暴露的INI指令名字冲突. 对于私有的扩展来说, 这个前缀不是必须的, 但是对于商业化或开源发布的公开扩展还是鼓励这样做的.
访问级别
对于INI指令, 开始总是有一个默认值. 多数情况下, 理想是保持默认值不变; 然而, 对于某些特殊的环境或者脚本内特定的动作, 这些值可能需要被修改. 如下表所示, INI指令的值可能在下面3个点被修改:
访问级别 | 含义 |
SYSTEM | 位于php.ini中,或者apache的httpd.conf配置文件中 |
PERDIR | 位于Apache的httpd.conf配置文件中 |
USER | 一旦脚本开始执行,就只能通过调用用户空间函数ini_set()去修改INI设置了. |
某些设置如果可以在任何地方被修改就没有多大意义了, 比如safe_mode, 如果可以在任何地方去修改, 那么恶意脚本的作者就可以很简单的去禁用safe_mode, 接着去读或修改本不允许操作的文件.
类似的, 某些非安全相关的指令比如register_globals或magic_quotes_gpc, 在脚本中不能被修改, 因为, 在脚本执行时, 它所影响的事情已经发生过了.
这些指令的访问控制是通过PHP_INI_ENTRY()的第三个参数完成的. 在你前面例子中, 使用了PHP_INI_ALL, 它的定义是一个位域操作: PHP_INI_SYSTEM | PHP_INI_PERDIR | PHP_INI_USER.
对于register_globals和magic_quotes_gpc这样的指令, 定义的访问级别为PHP_INI_SYSTEM | PHP_INI_PERDIR. 排除了PHP_INI_USER将导致以这个名字调用ini_set()时最终会失败.
现在, 你可能已经猜到, safe_mode和open_basedir这样的指令应该仅被定义为PHP_INI_SYSTEM. 这样的设置就确保了只有系统管理员可以修改这些值, 因为只有它们可以访问修改php.ini或httpd.conf文件中的配置.
修改事件
当INI指令被修改时, 无论是通过ini_set()函数还是某个perdir指令的处理, 引擎都会为其测试OnModify回调. 修改处理器可以使用ZEND_INI_MH()宏定义, 并通过在OnModify参数上传递函数名附加到INI指令上:
ZEND_INI_MH(php_sample4_modify_greeting) { if (new_value_length == 0) { return FAILURE; } return SUCCESS; } PHP_INI_BEGIN() PHP_INI_ENTRY("sample4.greeting", "Hello World", PHP_INI_ALL, php_sample4_modify_greeting) PHP_INI_END()
通过在new_value_length为0时返回FAILURE, 这个修改处理器禁止将greeting设置为空字符串. ZEND_INI_MH()宏产生的整个原型如下:
int php_sample4_modify_greeting(zend_ini_entry *entry, char *new_value, uint new_value_length, void *mh_arg1, void *mh_arg2, void *mh_arg3, int stage TSRMLS_DC);
各个参数的含义见下表:
参数名 | 含义 |
entry | 指向引擎真实存储的INI指令项.这个结构体提供了当前值,原始值,所属模块,以及其他一些下面代码(zend_ini_entry结构体结构)列出的信息 |
new_value | 要被设置的值.如果处理器返回SUCCESS,这个值将被设置到entry->value,同时如果entry->orig_value当前没有设置,则将当前值设置到entry->orig_value中,并设置entry->modified标记.这个字符串的长度通过new_value_length传递. |
mh_arg1, 2, 3 | 这3个指针对应INI指令定义时给出的数据指针(zend_ini_entry结构体中的3个同名成员).实际上,这几个值是引擎内部处理使用的,你不需要关心它们. |
stage | ZEND_INI_STAGE_系列的5个值之一: STARTUP, SHUTDOWN, ACTIVATE, DEACTIVATE, RUNTIME. 这些常量对应于MINIT, MSHUTDOWN, RINIT, RSHUTDOWN,以及活动脚本执行. |
核心结构体: zend_ini_entry
struct _zend_ini_entry { int module_number; int modifiable; char *name; uint name_length; ZEND_INI_MH((*on_modify)); void *mh_arg1; void *mh_arg2; void *mh_arg3; char *value; uint value_length; char *orig_value; uint orig_value_length; int modified; void ZEND_INI_DISP(*displayer); };
展示INI设置
在上一章, 你看到了MINFO函数以及相关的指令用于展示扩展的信息. 由于扩展暴露INI指令是很常见的, 因此引擎提供了一个公共的宏可以放置到PHP_MINFO_FUNCTION()中用于展示INI指令信息.
PHP_MINFO_FUNCTION(sample4) { DISPLAY_INI_ENTRIES(); }
这个宏将迭代PHP_INI_BEGIN()和PHP_INI_END()宏之间定义的INI指令集和, 在一个3列的表格中展示它们的INI指令名, 原始值(全局的), 以及当前值(经过PERDIR指令或ini_set()调用修改后)
默认情况下, 所有的指令都直接以其字符串形式输出. 对于某些指令, 比如布尔值以及用于语法高亮的颜色值, 则在展示处理时应用了其他格式. 这些格式是通过每个INI设置的显示处理器处理的, 它和你看到的OnModify一样是一个动态的回调函数指针.
显示处理器可以使用PHP_INI_ENTRY()宏的扩展版指定, 它接受一个额外的参数. 如果设置为NULL, 则使用展示字符串值的处理器作为默认处理器:
PHP_INI_ENTRY_EX("sample4.greeting", "Hello World", PHP_INI_ALL, php_sample4_modify_greeting, php_sample4_display_greeting)
显然, 需要在INI设置定义之前声明这个函数. 和OnModify回调函数一样, 这可以通过一个包装宏以及少量编码完成:
#include "SAPI.h" /* needed for sapi_module */ PHP_INI_DISP(php_sample4_display_greeting) { const char *value = ini_entry->value; /* 选择合适的当前值或原始值 */ if (type == ZEND_INI_DISPLAY_ORIG && ini_entry->modified) { value = ini_entry->orig_value; } /* 使得打招呼的字符串粗体显示(当以HTML方式输出时) */ if (sapi_module.phpinfo_as_text) { php_printf("%s", value); } else { php_printf("<b>%s</b>", value); } }
绑定到扩展的全局空间
所有的INI指令都在Zend引擎内有一块存储空间, 可以用以跟踪脚本内的变更并进行请求外部的全局设置维护. 在这块存储空间中, 所有的INI指令都以字符串值存储. 你已经知道了, 这些值可以使用INI_INT(), INI_FLT(), INI_BOOL()等宏函数, 很简单的翻译成其他的标量类型.
这个查找和转换过程由于两个原因非常低效: 首先, 每次一个INI的值在获取时, 它必须通过名字在一个HashTable中进行定位. 这种查找方式对于仅在运行时编译的用户空间脚本而言是没有问题的, 但是对于已编译的机器代码源, 运行时做这个工作就毫无意义.
每次请求标量值的时候都需要将底层的字符串值转换到标量值是非常低效的. 因此我们使用你已经学习过的线程安全全局空间作为存储媒介, 每次INI指令值变更时更新它即可. 这样, 所有访问INI指令的代码都只需要查找你的线程安全全局空间结构体中的某个指针即可, 这样就获得了编译期优化的优点.
在你的php_sample4.h文件MODULE_GLOBALS结构体中增加const char *greeting; 接着更新sample4.c中的下面两个方法:
ZEND_INI_MH(php_sample4_modify_greeting) { /* Disallow empty greetings */ if (new_value_length == 0) { return FAILURE; } SAMPLE4_G(greeting) = new_value; return SUCCESS; } PHP_FUNCTION(sample4_hello_world) { php_printf("%s\n", SAMPLE4_G(greeting)); }
由于这是对INI访问的一种非常常见的优化方式, 因此引擎暴露了一组专门处理INI指令到全局变量的绑定宏:
STD_PHP_INI_ENTRY_EX("sample4.greeting", "Hello World", PHP_INI_ALL, OnUpdateStringUnempty, greeting, zend_sample4_globals, sample4_globals, php_sample4_display_greeting)
这个宏执行和上面你自己的php_sample4_modify_greeting相同的工作, 但它不需要OnModify回调. 取而代之的是, 它使用了一个泛化的修改回调OnUpdateStringUnempty, 以及信息应该存储的空间. 如果要允许空的greeting指令值, 你可以直接指定OnUpdateString替代OnUpdateStringUnempty.
类似的, INI指令也可以绑定long, double, zend_bool的标量值. 在你的php_sample4.h中MODULE_GLOBALS结构体上增加几个字段:
long mylong; double mydouble; zend_bool mybool;
现在在你的PHP_INI_BEGIN()/PHP_INI_END()代码块中使用STD_PHP_INI_ENTRY()宏创建新的INI指令, 它和对应的_EX版本的宏的区别只是显示处理器以及绑定到的值不同.
STD_PHP_INI_ENTRY("sample4.longval", "123", PHP_INI_ALL, OnUpdateLong, mylong, zend_sample4_globals, sample4_globals) STD_PHP_INI_ENTRY("sample4.doubleval", "123.456", PHP_INI_ALL, OnUpdateDouble, mydouble, zend_sample4_globals, sample4_globals) STD_PHP_INI_ENTRY("sample4.boolval", "1", PHP_INI_ALL, OnUpdateBool, mybool, zend_sample4_globals, sample4_globals)
这里要注意, 如果调用了DISPLAY_INI_ENTRIES(), 布尔类型的INI指令"sample4.boolval"将和其他设置一样, 被显示为它的字符串值; 然而, 首选的布尔值指令应该被显示为"on"或"off". 要使用这些更加表意的显示, 你可以使用STD_PHP_INI_ENTRY_EX()宏并创建显示处理器, 或者使用另外一个宏:
STD_PHP_INI_BOOLEAN("sample4.boolval", "1", PHP_INI_ALL, OnUpdateBool, mybool, zend_sample4_globals *, sample4_globals)
这个特定类型的宏是布尔类型特有的, 它提供的是将布尔值转换为"on"/"off"值的显示处理器.
小结
在本章, 你了解了php语言中最古老的特性之一的实现, 它也是阻碍php可移植的罪魁. 对于每个新的INI设置, 都会使得编写可移植代码变得更加复杂. 使用这些特性要非常慎重, 因为扩展以后时钟都要使用它了. 并且, 在使用时要注意不同系统间的行为一致性, 以免在维护时出现不可预期的状况.
接下来的三张, 我们将深入到流API, 开始使用流的实现层和包装操作, 上下文, 过滤器等.
以上就是[翻译][php扩展开发和嵌入式]第13章-php的INI设置 的内容,更多相关内容请关注PHP中文网(www.php.cn)!