声明和使用 INI 设置
本章详细介绍了 PHP 如何使用其配置,以及如何通过注册和使用 INI 设置将扩展挂入 PHP 的主要配置步骤。
INI 设置提醒
在继续之前,您必须记住 INI 设置和 PHP 配置在 PHP 中的工作方式。以下是再次提取为源代码解释的步骤。PHP INI 文件解析步骤发生在 php_init_config() 中,与 INI 相关的所有内容主要发生在 Zend/zend_ini.c 中。
首先,PHP 尝试解析一个或多个 INI 文件。这些文件可能会声明一些设置,这些设置将来可能会或可能不会相关。在这个非常早期的阶段(INI 文件解析),PHP 不知道这些文件中会有什么。它只是解析内容,并保存此内容以供以后使用。
然后作为第二步,PHP 启动其扩展,调用它们的 MINIT()
。如果您需要记住 PHP 生命周期,请阅读专门的章节。MINIT()
现在可以注册当前感兴趣的扩展 INI 设置。在注册设置时,引擎会检查它是否之前解析过其定义,作为 INI 文件解析步骤的一部分。如果是这种情况,则 INI 设置将注册到引擎中,并获取从 INI 文件中解析的值。如果在解析的 INI 文件中没有定义,则将使用扩展设计者为 API 提供的默认值进行注册。
设置将获取的默认值是从 INI 文件解析中探测到的。如果没有找到,则默认值是扩展开发人员给出的,而不是相反。 |
我们在这里讨论的默认值称为 “主值(master value)”。您可能从 phpinfo() 输出中还记得它,对吧?

主值不能改变。如果在请求期间,用户想要更改配置,例如使用 ini_set(),并且如果允许,则更改的值将是 “本地值(local value)”,即当前请求的当前值。引擎将在请求结束时自动将本地值恢复为主值,从而重置它并忘记请求实时更改。
ini_get()
读取当前请求绑定的本地值,而 get_cfg_var()
无论发生什么情况都会读取主值。
如果您理解正确,那么对于任何不属于 INI 文件解析的值, |
关于 INI 设置
在引擎中,INI 设置由 zend_ini_entry
结构表示:
struct _zend_ini_entry {
zend_string *name;
int (*on_modify)(zend_ini_entry *entry, zend_string *new_value, void *mh_arg1, void *mh_arg2, void *mh_arg3,
int stage);
void *mh_arg1;
void *mh_arg2;
void *mh_arg3;
zend_string *value;
zend_string *orig_value;
void (*displayer)(zend_ini_entry *ini_entry, int type);
int modifiable;
int orig_modifiable;
int modified;
int module_number;
};
这种结构没有什么真正强大的功能。
-
设置的名称(
name
)和值(value
)是最常用的字段。但请注意,该值是一个字符串(如zend_string *
),而不是其他内容。 -
然后,就像我们在上面的介绍章节中详细介绍的那样,我们找到了
orig_value
、orig_modified
、modifiable
和modified
字段,它们与设置值的修改有关。设置必须在内存中保留其原始值(作为 “主值”, master value)。 -
modifiable 表示设置是否真正可修改,并且必须从
ZEND_INI_USER
、ZEND_INI_PERDIR
、ZEND_INI_SYSTEM
或ZEND_INI_ALL
中选择一些值,这些值可以一起标记,并在 PHP 手册 中详细说明。 -
每次在请求期间修改设置时,
modified
都会设置为 1,以便引擎在请求关闭时知道它必须将该 INI 设置值恢复为其主值,以便处理下一个请求。 -
on_modify()
是一个处理程序,每当当前设置的值被修改时都会被调用,例如使用对ini_set()
的调用(但不限于此)。我们稍后会更深入地讨论on_modify()
,但可以将其视为一个 验证器函数(例如,如果设置预期表示一个整数,您可以根据整数验证将给出的值)。它还可以作为更新全局值的记忆桥梁,我们稍后也会回到这一点。 -
diplayer()
不太有用,通常不会传递任何值。displayer()
是关于如何显示您的设置。例如,您可能还记得 PHP 倾向于将布尔值 true/yes/on/1 等显示为 On。这是displayer()
的工作:将当前值转换为 “显示” 值。
您还需要处理这个结构 zend_ini_entry_def
:
typedef struct _zend_ini_entry_def {
const char *name;
ZEND_INI_MH((*on_modify));
void *mh_arg1;
void *mh_arg2;
void *mh_arg3;
const char *value;
void (*displayer)(zend_ini_entry *ini_entry, int type);
int modifiable;
uint name_length;
uint value_length;
} zend_ini_entry_def;
zend_ini_entry_def
与 zend_ini_entry
非常相似,当程序员(你)必须在引擎中注册一个 INI 设置时,就会用到 zend_ini_entry_def
。引擎读取 zend_ini_entry_def
,并根据你提供的定义模型在内部创建一个 zend_ini_entry
供自己使用。很简单。
注册和使用 INI 条目
注册
INI 设置在请求过程中始终有效。它们可能会在请求期间在运行时更改其值,但在请求关闭时它们会恢复为原始值。因此,在扩展的 MINIT()
钩子中,注册 INI 设置在一次完成后即可完成。
您必须做的是声明 zend_ini_entry_def
的向量,专用宏将为您提供帮助。然后,您将向量注册到引擎,声明就完成了。让我们通过上一章关于随机数选择和猜测的示例来看一下,现在再次仅显示相关部分:
PHP_INI_BEGIN()
PHP_INI_ENTRY("pib.rnd_max", "100", PHP_INI_ALL, NULL)
PHP_INI_END()
PHP_MINIT_FUNCTION(pib)
{
REGISTER_INI_ENTRIES();
return SUCCESS;
}
PHP_MINFO_FUNCTION(pib)
{
DISPLAY_INI_ENTRIES();
}
PHP_MSHUTDOWN_FUNCTION(pib)
{
UNREGISTER_INI_ENTRIES();
return SUCCESS;
}
这是最简单的 INI 声明,我们不会保持原样,但步骤很简单:使用 PHP_INI_BEGIN
和 PHP_INI_END
宏声明一个 zend_ini_entry_def[]
向量。在中间,您再次使用宏添加自己的 zend_ini_entry_def
条目。我们使用最简单的一个:PHP_INI_ENTRY()
,它只需要四个参数:要注册的条目的名称、如果它不是 INI 文件扫描的一部分则给出的默认值(有关详细信息,请参阅上一章)、修改级别、PHP_INI_ALL
表示 “无处不在”。我们还没有使用验证器,并传递了 NULL
。
在 MINIT
hook 中,我们使用 REGISTER_INI_ENTRIES
宏来完成描述工作,而其对应部分 UNREGISTER_INI_ENTRIES
则用于在模块关闭时释放分配的资源。
现在,新的 “pib.rnd_max” INI 设置被声明为 PHP_INI_ALL
,这意味着用户可以使用 ini_set()
修改其值(并使用 ini_get()
将其读回)。
我们没有忘记使用 DISPLAY_INI_ENTRIES()
将这些 INI 设置显示为扩展信息的一部分。在 MINFO()
钩子的声明中忘记这一点将导致我们的 INI 设置在信息页面(phpinfo()
)中对用户隐藏。如果需要,请查看 扩展信息章节。
使用
作为扩展开发人员,我们现在可能需要您自己读取 INI 设置值。在扩展中执行此操作的最简单方法是使用宏,这些宏将在保留所有 INI 设置的主数组中查找值,找到它并将其返回为您要求的类型。我们根据想要返回的 C 类型提供了几个宏。
INI_INT(val)
、INI_FLT(val)
、INI_STR(val)
、INI_BOOL(val)
所有四个宏都将从 INI 设置数组中查找提供的值并将其返回(如果找到)并转换为您要求的类型。
请记住,在 |
示例:
php_printf("The value is : %lu", INI_INT("pib.rnd_max"));
如果找不到该值,则返回 0,因为我们要求的是 long。如果是浮点数转换等,也会返回 0.0。 |
如果用户修改了设置,而我们想要显示 “主” 原始值(在我们的例子中是 100),那么我们将使用 INI_ORIG_INT()
而不是 INI_INT()
。当然,其他类型也存在此类偏差宏。
验证器和全局内存桥
到目前为止一切顺利,注册和读回 INI 设置值并不难。但我们在前面几行中使用它们的方式远非最佳。
使用 “高级” INI 设置 API 可以同时解决两个问题:
-
每次我们想要读取我们的值时,都需要查找主 INI 设置表,以及转换为正确的类型(通常)。这些操作会花费一些 CPU 周期。
-
我们没有提供任何验证器,因此用户可以更改我们的设置并将任何他想要的内容作为值。
解决方案是使用 on_modify()
验证器和内存桥来更新全局变量。
通过使用高级 INI 设置管理 API,我们可以告诉引擎正常注册我们的设置,但我们也可以指示它在每次更改 INI 设置值时更新我们喜欢的全局变量。因此,每当我们想要读回我们的值时,我们只需要读取我们的全局变量。在我们需要经常读取 INI 设置值的情况下,这将提高性能,因为不再需要哈希表查找和转换操作。
您需要熟悉全局变量才能继续阅读本章。全局空间管理将 单独成章。 |
要将内存桥声明为全局变量,我们需要创建一个请求全局变量,并更改声明 INI 设置的方式。如下所示:
ZEND_BEGIN_MODULE_GLOBALS(pib)
zend_ulong max_rnd;
ZEND_END_MODULE_GLOBALS(pib)
ZEND_DECLARE_MODULE_GLOBALS(pib)
PHP_INI_BEGIN()
STD_PHP_INI_ENTRY("pib.rnd_max", "100", PHP_INI_ALL, OnUpdateLongGEZero, max_rnd, zend_pib_globals, pib_globals)
PHP_INI_END()
PHP_MINIT_FUNCTION(pib)
{
REGISTER_INI_ENTRIES();
return SUCCESS;
}
我们声明一个名为 max_rnd
的全局变量,类型为 zend_ulong
。然后,我们这次使用 STD_PHP_INI_ENTRY()
注册我们的 “pib.rnd_max” INI 值。这允许我们将更多参数传递给宏。前三个是已知的,我们在本章之前详细介绍了它们。
最后四个参数代表全局桥。我们告诉我们想要更新 max_rnd
,在 zend_pib_globals
结构中,由符号 pib_globals
表示。如果不舒服,请阅读全局管理章节。快速提醒一下,ZEND_BEGIN_MODULE_GLOBALS() 声明 zend_pib_globals 结构,而 ZEND_DECLARE_MODULE_GLOBALS() 声明这种类型的 pib_globals 符号。
我们声明一个名为 max_rnd
的全局变量,其类型为 zend_ulong
。然后,我们使用 STD_PHP_INI_ENTRY()
注册我们的 “pib.rnd_max” INI 值。这样我们就可以向宏传递更多参数。前三个参数是已知的,我们在本章中已经详细介绍过。
最后四个参数代表 globals 桥接。我们要更新由 pib_globals
符号表示的 zend_pib_globals
结构中的 max_rnd
。如果不习惯,请阅读全局管理章节。作为一个快速提醒,ZEND_BEGIN_MODULE_GLOBALS()
声明了 zend_pib_globals
结构,而 ZEND_DECLARE_MODULE_GLOBALS()
声明了这样一个类型的 pib_globals 符号。
在内部, offsetof 将用于计算 |
这里使用的 on_modify()
验证器 onUpdateLongGEZero()
是 PHP 中存在的默认验证器,用于验证值是否大于或等于零。全局变量需要此验证器才能更新,因为此工作已在验证器中完成。
现在,要读回我们的 INI 设置值,我们只需读取 max_rnd
全局变量的值:
php_printf("The value is : %lu", PIB_G(max_rnd));
大功告成。
现在让我们来看看验证器(on_modify()
处理程序)。验证器有两个目标:
-
验证传递的值
-
如果验证成功,则更新全局变量
只有在 INI 设置被设置或修改(写入)时,才会调用验证器。
如果您希望全局变量使用 INI 设置值进行更新,则需要验证器。这种机制不是由引擎神奇地执行的,而必须明确地在验证器中执行。 |
我们看一下 onUpdateLongGEZero()
源代码:
#define ZEND_INI_MH(name) int name(zend_ini_entry *entry, zend_string *new_value,
void *mh_arg1, void *mh_arg2, void *mh_arg3, int stage)
ZEND_API ZEND_INI_MH(OnUpdateLongGEZero)
{
zend_long *p, tmp;
#ifndef ZTS
char *base = (char *) mh_arg2;
#else
char *base;
base = (char *) ts_resource(*((int *) mh_arg2));
#endif
tmp = zend_atol(ZSTR_VAL(new_value), (int)ZSTR_LEN(new_value));
if (tmp < 0) {
return FAILURE;
}
p = (zend_long *) (base+(size_t) mh_arg1);
*p = tmp;
return SUCCESS;
}
如您所见,没有什么复杂的。您的验证器被赋予了 new_value
,并且必须根据它进行验证。请记住,new_value
的类型为 zend_string *
。onUpdateLongGEZero()
将值作为 long 并检查它是否为正整数。如果一切顺利,验证器必须返回 SUCCESS
,否则返回 FAILURE
。
然后是更新全局的部分。mh_arg
变量用于将任何类型的信息传送到您的验证器。
‘mh’ 代表修改处理程序。验证器回调也称为 修改处理程序回调。 |
mh_arg2
是一个指向内存区域的指针,该内存区域代表全局结构内存的开头,在我们的例子中,是 pib_globals
分配内存的开头。请注意,当我们谈论请求全局变量内存时,无论您是否使用 ZTS 模式,后者的访问方式都不同。有关 ZTS 的更多信息, 请参见此处。
mh_arg1
传递了全局成员的计算偏移量(对于我们来说是 max_rnd
),您必须自己对内存进行切片以获取指向它的指针。这就是为什么我们将 mh_arg2
存储为通用 char *
指针并将 mh_arg1
转换为 size_t
的原因。
然后,您只需通过写入指针来使用经过验证的值更新内容。mh_arg3
实际上未使用。
PHP 的默认验证器是 OnUpdateLongGEZero()
、OnUpdateLong()
、OnUpdateBool()
、OnUpdateReal()
、OnUpdateString()
和 OnUpdateStringUnempty()
。它们的名称和源代码都是自描述的(你可以阅读它)。
基于这样的模型,我们可以开发自己的验证器,用于验证 0
到 1000
之间的正整数,例如:
ZEND_INI_MH(onUpdateMaxRnd)
{
zend_long tmp;
zend_long *p;
#ifndef ZTS
char *base = (char *) mh_arg2;
#else
char *base;
base = (char *) ts_resource(*((int *) mh_arg2));
#endif
p = (zend_long *) (base+(size_t) mh_arg1);
tmp = zend_atol(ZSTR_VAL(new_value), (int)ZSTR_LEN(new_value));
if (tmp < 0 || tmp > 1000) {
return FAILURE;
}
*p = tmp;
return SUCCESS;
}
PHP_INI_BEGIN()
STD_PHP_INI_ENTRY("pib.rnd_max", "100", PHP_INI_ALL, onUpdateMaxRnd, max_rnd, zend_pib_globals, pib_globals)
PHP_INI_END()
一旦检查了范围(验证器会执行此操作),就可以安全地将长整型写入无符号长整型。 |
现在,如果用户想要修改设置并传递一个未经验证的错误值,ini_set()
只会向用户空间返回 false
,并且不会修改该值:
ini_set('pib.rnd_max', 2048); /* returns false as 2048 is > 1000 */
相反,ini_set()
返回旧值并修改当前值。新提供的值将成为当前 “本地(local)” 值,而默认的前一个值则保留为 “主值,master value”。phpinfo()
或 ini_get_all()
详细说明了这些值。示例:
ini_set('pib.rnd_max', 500);
var_dump(ini_get_all('pib'));
/*
array(1) {
["pib.rnd_max"]=>
array(3) {
["global_value"]=>
string(3) "100"
["local_value"]=>
string(3) "500"
["access"]=>
int(7)
}
*/
请注意,每次值更改时都会调用验证器回调,并且会更改多次。例如,对于我们的小示例,我们设计的验证器被调用三次:
-
一次在
REGISTER_INI_ENTRY()
中,一次在MINIT()
中。我们在此处将默认值设置为我们的设置,因此这是使用我们的验证器完成的。请记住,默认值可能来自 INI 文件解析。 -
每次
ini_set()
用户空间调用一次。 -
一次在
RSHUTDOWN()
中,如果值在当前请求期间已更改,则引擎将尝试将本地值恢复为其主值。用户空间ini_restore()
执行相同的工作。
还请记住,值访问器由 ini_set()
检查。如果我们设计了一个 PHP_INI_SYSTEM
设置,那么用户将无法使用 ini_set()
修改它,因为 ini_set()
使用 PHP_INI_USER
作为访问器。然后就会检测到不匹配的情况,在这种情况下,引擎就不会调用验证器。
如果需要在运行时更改扩展的 INI 设置值,内部调用 zend_alter_ini_entry()
,这也是用户态 ini_set()
所使用的。
使用显示器
关于 INI 设置,您需要了解的最后一件事是 displayer()
回调。它在实践中用得较少,每当用户空间要求 “打印” 您的 INI 设置值时,即通过使用 phpinfo()
或 php --ri
,它都会被触发。
如果您不提供显示器,则将使用默认显示器。查看它:
> php -dextension=pib.so -dpib.rnd_max=120 --ri pib
Directive => Local Value => Master Value
pib.rnd_max => 120 => 120
默认显示器采用 INI 设置值(提醒一下,其类型为 zend_string *
),并简单地显示它。如果未找到任何值或值为空字符串,则显示字符串 “no value”。
要实现这样的过程,我们必须声明将被调用的 displayer()
回调。让我们尝试将我们的 “pib.rnd_max” 值表示为百分比条,带有 “#” 和 “.” 字符。仅举一个例子:
#define ZEND_INI_DISP(name) void name(zend_ini_entry *ini_entry, int type)
ZEND_INI_DISP(MaxRnd)
{
char disp[100] = {0};
zend_ulong tmp = 0;
if (type == ZEND_INI_DISPLAY_ORIG && ini_entry->modified && ini_entry->orig_value) {
tmp = ZEND_STRTOUL(ZSTR_VAL(ini_entry->orig_value), NULL, 10);
} else if (ini_entry->value) {
tmp = ZEND_STRTOUL(ZSTR_VAL(ini_entry->value), NULL, 10);
}
tmp /= 10;
memset(disp, '#', tmp);
memset(disp + tmp, '.', 100 - tmp);
php_write(disp, 100);
}
PHP_INI_BEGIN()
STD_PHP_INI_ENTRY_EX("pib.rnd_max", "100", PHP_INI_ALL, onUpdateMaxRnd, max_rnd, zend_pib_globals,
pib_globals, MaxRnd)
PHP_INI_END()
这次我们使用 _EX()
宏对应部分来声明我们的 INI 设置。此宏接受显示器函数作为最后一个参数。然后使用 STD_PHP_INI_ENTRY_EX()
。
然后使用 ZEND_INI_DISP()
来声明我们的显示器函数。它接收已附加到的 INI 设置作为参数,以及 PHP 希望您显示的值:ZEND_INI_DISPLAY_ORIG
表示主值,ZEND_INI_DISPLAY_ACTIVE
表示当前请求绑定的本地值。
然后,我们玩弄这个值,将其表示为 “#” 和 “.” 字符,类似于这样:
ini_set('pib.rnd_max', 500);
phpinfo(INFO_MODULES);
如果我们用:
> php -dextension=pib.so /tmp/file.php
然后显示:
pib
Directive => Local Value => Master Value
pib.rnd_max => ##################################################..................................................
=> ##########..........................................................................................
如果我们用:
> php -dextension=pib.so -dpib.rnd_max=10 /tmp/file.php
然后显示:
pib
Directive => Local Value => Master Value
pib.rnd_max => ##################################################..................................................
=> ....................................................................................................
由于 PHP 将同时显示本地值和主值,我们的显示器回调将在此处被调用两次。本地值实际上代表的是 “500”,而主值显示的是默认的硬编码值 “100”(如果我们不更改它),如果我们使用 php-cli 中的 -d
来更改它,它实际上就被使用了。
如果想使用 PHP 中现有的显示程序,可以使用 zend_ini_boolean_displayer_cb()
、zend_ini_color_displayer_cb()
或 display_link_numbers()
。