13.1 声明和访问INI设置

INI条目被定义在一个完整的独立的的块,位于上文中所说的MINIT方法的同一个源文件,并且用下面的一对宏来定义,并在这对宏之间放入一个或者多个条目PHP_INI_BEGIN()PHP_INI_END()

这些宏方法和上一章所提到的ZEND_BEGIN_MODULE_GLOBALS()ZEND_END_MODULE_GLOBALS()有着相同的用法。这些结构是用静态数据的实例来声明,而不仅仅是提供一个结构的定义

  1. static zend_ini_entry ini_entries[] = {
  2. {0,0,NULL,0,NULL,NULL,NULL,NULL,NULL,0,NULL,0,0,NULL} };

正如你所看到的,上面定义了zend_ini_entry的一个向量值并以一个空记录来终止。你或许已经多次在function_entry结构的定义之中看到过这种以填充静态向量的方法

简单INI设置

现在你可以使用 INI 结构来声明条目,这个机制是用来注册和销毁一些在机器上的设置,你可以声明一些对你的扩展有用的有实际意义的设置了。

假设你的扩展的方法就像你在第五章看到的那个例子(Your First Extension)一样,只是输出一个简单的问候,你可能想让你要输出的问候语句是可定制的:

  1. PHP_FUNCTION(sample4_hello_world)
  2. {
  3. php_printf("Hello World!\n");
  4. }

最简单的方法就是定义一个 INI 的设置,让它的默认值为Hello World!,像下面这样:

  1. #include "php_ini.h"
  2. PHP_INI_BEGIN()
  3. PHP_INI_ENTRY("sample4.greeting", "Hello World", PHP_INI_ALL, NULL)
  4. PHP_INI_END()

正如你猜测的一样,PHP_INI_ENTRY 这个宏里面设置的前面的两个参数,分别代表着INI设置的名称和它的默认值。第三个参数决定设置是否允许被修改,以及它能被修改的作用域。最后一个参数是一个回调函数,当INI的值被修改时候触发此回调函数。你将会在某些修改事件的地方详细的了解这个参数。

PHP总共有4个指令配置作用域:(PHP中的每个指令都有自己的作用域,指令只能在其作用域中修改,不是任何地方都能修改配置指令的)

Parameter Meaning
PHP_INI_PERDIR 指令可以在php.ini、httpd.conf或.htaccess文件中修改
PHP_INI_SYSTEM 指令可以在php.ini 和 httpd.conf 文件中修改
PHP_INI_USER 指令可以在用户脚本中修改
PHP_INI_ALL 指令可以在任何地方修改

现在你已经声明了你的INI的设置,你现在准备将它用在你的问候函数之中:

  1. PHP_FUNCTION(sample4_hello_world)
  2. {
  3. const char *greeting = INI_STR("sample4.greeting");
  4. php_printf("%s\n", greeting);
  5. }

有一点很重要:char*类型的值被认为是属于ZEND引擎的,是不能修改的。正因为如此,你将本地变量设置进INI时候,它将在你的方法中被声明为const。并不是所有的INI值都是基于字符串的;也有其他的一些用于整数、浮点数、或布尔值的宏,例如:

  1. long lval = INI_INT("sample4.intval");
  2. double dval = INI_FLT("sample4.fltval");
  3. zend_bool bval = INI_BOOL("sample4.boolval");

通常你想知道你当前的INI设置的值;恭喜你,ZEND内核刚好就存在一组这样的宏为你提供查询每种类型的INI的默认值。

  1. const char *strval = INI_ORIG_STR("sample4.stringval");
  2. long lval = INI_ORIG_INT("sample4.intval");
  3. double dval = INI_ORIG_FLT("sample4.fltval");
  4. zend_bool bval = INI_ORIG_BOOL("sample4.boolval");
NOTE
  1. 在这个例子中,`sample4.greeting`只是INI设置的一个前缀,用来帮助保证它的设置不会和其他扩展的设置相冲突。这种加前缀的方法不仅仅是用在闭源的扩展上,它更被广泛的用在任何商业或者开源的公开发布的扩展上面。

访问级别

每一个给出的INI值总是有默认值的。在许多情况下,INI都有一个比较合理的默认值;然而,在某些比较特殊的环境或者某个脚本要做一些比较特别的事情的情况下,这些INI值通常会被修改。这些设置可以在任何的三个不同的点被修改,看下表:

Access Level Meaning
SYSTEM 设置被放在php.ini中,或者在Apache的http.conf配置文件中的,它在apache启动时候生效,被认为是设置的全局变量
PERDIR 一些设置被放在Apache的http.conf的或者块中,或者.htaccess文件之中。原文:Any setting found in a or block within Apache’s httpd.conf, or settings located in .htaccess files as well as certain other locations not exclusive to Apache are processed just prior to a given request if that request is within the appropriate directory or virtual host.
USER 一旦脚本开始执行,唯一的改变INI设置的方法就是利用用户方法:ini_set()

某些设置,例如safe_mode,如果他们能在任何地方任意修改的话,那它的存在就没意义了。例如,一个恶意脚本的作者可以简单的禁用safe_mode,然后任意读取和修改其他的被禁止的文件。

同样,一些非安全相关的设置,如:register_globals或magic_quotes_gpc 不能在一个脚本中有效的被改变,因为他们所承担的任务已经过了。 Similarly, some non-security related settings such as register_globals or magic_quotes_gpc cannot be effectively changed within a script because the point at which they bear relevance has already passed.

这些设置是通过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 来声明。对于这些设置,在任何调用ini_set()的地方排除PHP_INI_USER的话将以失败告终(The exclusion of PHP_INI_USER results in any call to ini_set() for these settings ending in failure)。

现在你可能会猜测,safe_mode和open_basedir之类的设置只能用PHP_INI_SYSTEM来声明。这种设置可以确保只有系统管理员才能修改这些值,因为只有他们有权限修改php.ini或者http.conf中的值。

修改事件

无论INI设置在什么时候被修改,无论是通过ini_set()方法来修改还是在一个perdir指令执行期间来修改,zend引擎都会通过一个OnModify的回调来检查它。做修改的人可能会通过使用ZEND_INI_MH宏来定义,然后通过OnModify方法的参数来附加到INI设置里面:

  1. ZEND_INI_MH(php_sample4_modify_greeting)
  2. {
  3. if (new_value_length == 0) {
  4. return FAILURE;
  5. }
  6. return SUCCESS;
  7. }
  8. PHP_INI_BEGIN()
  9. PHP_INI_ENTRY("sample4.greeting", "Hello World",
  10. PHP_INI_ALL, php_sample4_modify_greeting)
  11. PHP_INI_END()

new_value_length的长度为0的时候返回FAILURE,这样修改者就可以禁止将祝福语句设置为一个空字符串。像下面这样使用ZEND_INI_MH()可以生成整个原型:

  1. int php_sample4_modify_greeting(zend_ini_entry *entry,
  2. char *new_value, uint new_value_length,
  3. void *mh_arg1, void *mh_arg2, void *mh_arg3,
  4. int stage TSRMLS_DC);
Table 13.2. INI Setting Modifier Callback Parameters
Parameter Meaning
enTRy 指向zend引擎实际存储的INI设置,这种结构提供一些信息,包括现在的值、原始值、拥有模块、还有其他的一些在下面的 Listing 13.1 中的详细信息
new_value 关于设置的值。如果处理方法返回SUCCESS,这个值将被填充进 enTRy->value ,如果 entry->orig_value 至今没有设置的话,当前的值将被旋转到这个位置,并且也会设置 enTRy->modified 这个标志。字符串的长度将被填充到 new_value_length。
mh_arg1,2,3 这组指针(三个一组)提供了访问数据指针最初给出的INI设置的声明。在实践中,这些值都是通过内部引擎进程来调用的,所以你不需要担心它们
stage ZEND_INI_STAGE_s 这种形式里面有五个值,这五个由s代表的值为STARTUP, SHUTDOWN, ACTIVATE, DEACTIVATE, 或者 RUNTIME,这些常量分别对应 MINIT, MSHUTDOWN, RINIT, RSHUTDOWN 还有 处于活跃状态正在执行的脚本。
Listing 13.1. Core structure: zend_ini_entry
  1. struct _zend_ini_entry {
  2. int module_number;
  3. int modifiable;
  4. char *name;
  5. uint name_length;
  6. ZEND_INI_MH((*on_modify));
  7. void *mh_arg1;
  8. void *mh_arg2;
  9. void *mh_arg3;
  10. char *value;
  11. uint value_length;
  12. char *orig_value;
  13. uint orig_value_length;
  14. int modified;
  15. void ZEND_INI_DISP(*displayer);
  16. };

显示INI设置

在上一章,你已经见过MINFO方法,还有一些其他关于扩展的基础知识。因为输出INI的信息对于一个扩展来说是很正常的,ZEND引擎有一个统一的宏来输出这些内容,它可以被放置在PHP_MINFO_FUNCTION()块中:

  1. PHP_MINFO_FUNCTION(sample4)
  2. {
  3. DISPLAY_INI_ENTRIES();
  4. }

这个宏需要INI设置的值,而这个值早已经在前面定义在了PHP_INI_BEGINPHP_INI_END宏之间。INI设置被迭代的显示在一个三列的表单中,这三列分别是INI设置的名字,它的原始(全局)设置,还有通过PERDIR指令或者ini_set()修改过的当前的设置。

默认情况下,所有的设置的条目都是根据原有的字符串来简单的输出的。(By default, all entries are simply output according to their string representation as-is.)一些类似布尔值和语法高亮显示的颜色的值,在显示的时候会经过特殊的格式化处理。这种特殊格式化的方法是通过每个INI设置自己的显示处理程序来处理的,这个处理程序是通过动态指向一个回调函数来实现的,跟我们前面看到的 OnModify 类似。

显示处理程序指定使用一个扩展的PHP_INI_ENTRY()宏的版本,PHP_INI_ENTRY()接受一个额外的参数。如果将它设置为NULL,默认的显示处理程序将按照字符串的值原样使用。

  1. PHP_INI_ENTRY_EX("sample4.greeting", "Hello World", PHP_INI_ALL,
  2. php_sample4_modify_greeting, php_sample4_display_greeting)

明显的,这个回调需要在INI设置使用之前提前的定义好。与 OnModify 的回调类似,这个用一个包装宏来完成,并且只用少量的代码就能实现:

  1. #include "SAPI.h" /* needed for sapi_module */
  2. PHP_INI_DISP(php_sample4_display_greeting)
  3. {
  4. const char *value = ini_entry->value;
  5. /* Select the current or original value as appropriate */
  6. if (type == ZEND_INI_DISPLAY_ORIG &&
  7. ini_entry->modified) {
  8. value = ini_entry->orig_value;
  9. }
  10. /* Make the greeting bold (when HTML output is enabled) */
  11. if (sapi_module.phpinfo_as_text) {
  12. php_printf("%s", value);
  13. } else {
  14. php_printf("<b>%s</b>", value);
  15. }
  16. }

绑定到扩展的全局设置

所有的INI条目都在Zend Engine中被给予了存储空间,用来在脚本之中追踪它的改变,并且在请求之外维持全局设置。在这个存储空间之中,所有的INI设置都是以字符串形式保存的。你应该会想到,这些值可以使用INI_INT(),INI_FLT(),和INI_BOOL()这类宏很容易地转换为标量值。

这个查询和转换过程非常低效的原因有两个,它必须通过名字位于一个hash表之中,所以每次都要重新获取。这种查找方式对于用户空间脚本是非常好的,因为一个脚本只有在运行时才会被编译,但是对于编译型的语言,做这个工作是毫无意义的。

对于标量来说,这个会导致更加的低效,因为底层的字符串值在每一次被请求时候都必须再转换一次。使用你已经知道的,你可以声明一个线程安全的全局存储介质,并在每次改变的时候使用新值的地址来更新它。然后,任何代码都可以通过在你的线程安全的结构之中查找指针来访问INI设置,这个可以利用编译时优化。

在php_sample4.h文件中,在MODULE_GLOBALS结构中添加const char *greeting;,然后更新位于 sample4.c中的两个方法:

  1. ZEND_INI_MH(php_sample4_modify_greeting)
  2. {
  3. /* Disallow empty greetings */
  4. if (new_value_length == 0) {
  5. return FAILURE;
  6. }
  7. SAMPLE4_G(greeting) = new_value;
  8. return SUCCESS;
  9. }
  10. PHP_FUNCTION(sample4_hello_world)
  11. {
  12. php_printf("%s\n", SAMPLE4_G(greeting));
  13. }

因为这是一个优化 INI 值存取的非常一般的方法,另一对宏是通过Zend引擎来导出的,将INI设置绑定到全局变量。(Because this is a common approach to optimizing INI access, another pair of macros is exported by the engine that handle binding INI settings to global variables)

  1. STD_PHP_INI_ENTRY_EX("sample4.greeting", "Hello World",
  2. PHP_INI_ALL, OnUpdateStringUnempty, greeting,
  3. zend_sample4_globals, sample4_globals,
  4. php_sample4_display_greeting)

这个条目和刚刚你不需要的回调执行相同的工作。相反,他使用一个通用目的的修饰回调 OnUpdateStringUnempty,并且连同信息在存储空间的存储位置。为了允许空白的问候语,你可以简单的指定 OnUpdateString修饰语,这样就比OnUpdateStringUnempty方法简单。

类似的方法,比如INI设置可能会被绑定到类似long,double和zend_bool之类的标量上。在你的php_sample4.h中添加三个条目到MODULE_GLOBALS 结构中:

  1. long mylong;
  2. double mydouble;
  3. zend_bool mybool;

现在使用STD_PHP_INI_ENTRY()宏在PHP_INI_BEGIN()/PHP_INI_END()块中创建INI条目,不同于它的 _EX版本的仅仅是缺少一个播放者方法以及绑定他们到你的新值:

  1. STD_PHP_INI_ENTRY("sample4.longval", "123",
  2. PHP_INI_ALL, OnUpdateLong, mylong,
  3. zend_sample4_globals, sample4_globals)
  4. STD_PHP_INI_ENTRY("sample4.doubleval", "123.456",
  5. PHP_INI_ALL, OnUpdateDouble, mydouble,
  6. zend_sample4_globals, sample4_globals)
  7. STD_PHP_INI_ENTRY("sample4.boolval", "1",
  8. PHP_INI_ALL, OnUpdateBool, mybool,
  9. zend_sample4_globals, sample4_globals)

注意在这一点上,如果DISPLAY_INI_ENTRIES()被调用,”sample4.boolval” 这个布尔类型的INI设置会像其他的设置一样以字符串被显示出来;然而,优先以字符串输出的是”on”或者“off”。为了确认这些显示的值是有意义的,你可以在后面的两个方法之中任选一个,其中一个是切换到STD_PHP_INI_ENTRY_EX()宏创建一个显示的方法,另一个是你可以任意使用可以帮到你的宏:

  1. STD_PHP_INI_BOOLEAN("sample4.boolval", "1",
  2. PHP_INI_ALL, OnUpdateBool, mybool,
  3. zend_sample4_globals *, sample4_globals)

这种特定类型的宏跟在INI家族中的布尔是不一样的,它仅仅提供一个显示处理者来将开启的设置为“on”,关闭的设置为”off”。