13.1 声明和访问INI设置

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

    正如你所看到的,上面定义了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中的每个指令都有自己的作用域,指令只能在其作用域中修改,不是任何地方都能修改配置指令的)

    现在你已经声明了你的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

    访问级别

    某些设置,例如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. if (new_value_length == 0) {
    3. return FAILURE;
    4. return SUCCESS;
    5. }
    6. PHP_INI_BEGIN()
    7. PHP_INI_ENTRY("sample4.greeting", "Hello World",
    8. PHP_INI_ALL, php_sample4_modify_greeting)
    9. 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
    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 类似。

    1. PHP_INI_ENTRY_EX("sample4.greeting", "Hello World", PHP_INI_ALL,

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

    绑定到扩展的全局设置

    所有的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. /* Disallow empty greetings */
    3. if (new_value_length == 0) {
    4. return FAILURE;
    5. }
    6. SAMPLE4_G(greeting) = new_value;
    7. return SUCCESS;
    8. }
    9. PHP_FUNCTION(sample4_hello_world)
    10. {
    11. php_printf("%s\n", SAMPLE4_G(greeting));
    12. }

    因为这是一个优化 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,