4.7. 声明和使用 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 文件解析中探查到的。如果未找到,则默认值为扩展开发人员提供的默认值,而不是其他方式。
我们在此讨论的默认值称为“主值”。你可以从phpinfo()
输出中调用它,对吗?
主值不能更改。如果在请求期间,用户希望使用ini_set()
来更改配置,并且如果允许的话,则更改后的值为“本地值”,即当前请求的当前值。引擎将在请求结束时自动将本地值恢复为主值,从而将其重置并忽略请求有效的更改。
ini_get()
读取当前请求绑定的本地值,而get_cfg_var()
无论发生什么都会读取主值。
注意
如果你理解正确的话,
get_cfg_var()
对于任何不属于 INI 文件解析的值,即使该值存在且由扩展声明,该值也将返回false。反之亦然:如果要求提供扩展没有声明的设置,则ini_get
将返回false,即使这样的设置是INI文件解析的一部分(例如php.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
和Modifyed
字段。该设置必须将其原始值(作为“主值”)保留在内存中。 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
非常相似,程序员(你)必须在引擎上注册 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_BEGIN()
,仅包含四个参数:要注册的条目的名称,如果它不是 INI 文件扫描的一部分,则给出其默认值(有关详细信息,请参见上一章),修改级别,PHP_INI_ALL
表示“随处”。我们还没有使用验证器,故传递了 NULL。
在MINIT
挂钩中,我们使用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设置数组中查找提供的值,并将其(如果找到)返回并强制转换为你要求的类型。
注意
请记住,在
zend_ini_entry
中,该值为zend_string
类型。在我们的示例中,我们注册了一个类型为“long”的 INI 设置,即我们的pib.rnd_max
,其默认值为100。但是那个值被作为zend_string
注册到 INI 设置数组中,因此每次我们想读回它的值时都需要将其强制转换为'long'。INI_INT()
做这样的工作。
例:
php_printf("The value is : %lu", INI_INT("pib.rnd_max"));
注意
如果找不到该值,则按照要求 long 返回0。在相同的情况下将返回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 值。这使我们可以将更多参数传递给宏。 前四个已知,我们将在本章前面详细介绍。
最后四个参数代表全局桥。我们告诉它要更新由符号pib_globals
表示的zend_pib_globals
结
构中的max_rnd
。如果不了解,阅读全局管理章节。 快速提醒一下,ZEND_BEGIN_MODULE_GLOBALS()
声明了Zend_pib_globals
结构,而ZEND_DECLARE_MODULE_GLOBALS()
则声明了这种类型的pib_globals
符号。
注意
在内部, 偏移量将用于将我们的
zend_pib_globals
结构里的max_rnd
成员的字节片计算,以便只要更改 'pub.rand_max' 就能更新该部分内存。
这里使用的on_modify()
验证器,,onUpdateLongGEZero()
是 PHP 中存在的默认验证器,用于对大于或等于零的 long 值进行验证。需要验证器来更新全局变量,验证器已完成了这样的工作。
现在,要读取我们的 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
变量用于将任何类型的信息携带到验证器中。
Note
'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()
Note
一旦确认范围,就可以安全地从 long 写入 unsigned long 。
现在,如果用户想要修改设置并传递一个不验证的错误值,则ini_set()
只会将false返回到用户区,并且不会修改该值:
ini_set('pib.rnd_max', 2048); /* returns false as 2048 is > 1000 */
在相反的情况下,ini_set()
返回旧值并修改当前值。新提供的值变为
当前的“本地”值,而默认的先前值保留为“主值”。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)
}
*/
建议您在更改值时将调用验证程序回调,该值会更改多次。例如,对于我们的小示例,我们设计的验证器被称为3次:
-在REGISTER_INI_ENTRY()
中的一次中,在MINIT()
中的一次中。我们在此处将默认值设置为我们的设置,因此这是使用我们的验证器完成的。请记住,默认值可能来自INI文件解析。
-每个ini_set()
用户域调用一次。
-一旦进入RSHUTDOWN()
,如果引擎在当前请求期间更改了本地值,则尝试将本地值恢复为其主值。 Userlandini_restore()
执行相同的工作。
还请记住,ini_set()
将检查值访问器。如果我们设计了PHP_INI_SYSTEM
设置,则用户将无法使用ini_set()
对其进行修改,因为ini_set()
使用的是 PHP_INI_USER`作为访问者。然后,在这种情况下,将检测到不匹配,并且引擎不会调用验证器。
如果您需要在运行时将INI设置值更改为扩展名,则内部调用为zend_alter_ini_entry()
,这就是userlandini_set()
所使用的。
使用显示器
关于INI设置,您需要了解的最后一件事是displayer()
回调。它在实践中较少使用,它在用户区要求打印''您的INI设置值时触发,即通过使用</code>phpinfo()<code>或</code>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->中的-d
进行了更改, cli,它得到了有效的利用。
如果要使用PHP中现有的显示器之一,则可以使用zend_ini_boolean_displayer_cb()
,zend_ini_color_displayer_cb()
或display_link_numbers()