首页 > PHP > PHP 内核与原生扩展开发 > 原生扩展设计

4.7. 声明和使用 INI 设置

声明和使用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()输出中调用它,对吗?

../../_images/php_extensions_ini.png

主值不能更改。如果在请求期间,用户希望使用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_valueorig_modifiedmodifiableModifyed字段。该设置必须将其原始值(作为“主值”)保留在内存中。
  • modifiable 告诉设置实际上是否可修改,以及必须从 ZEND_INI_USERZEND_INI_PERDIRZEND_INI_SYSTEMZEND_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_BEGINPHP_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设置值时触发,即通过使用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->中的-d进行了更改, cli,它得到了有效的利用。

如果要使用PHP中现有的显示器之一,则可以使用zend_ini_boolean_displayer_cb()zend_ini_color_displayer_cb()display_link_numbers()