运行时的配置
我们知道在 PHP 代码中,可以通过 ini_set 函数动态修改一些配置项,这又是怎么做的呢?接下来看下 ini_set 函数的具体实现。ini_set 函数的定义在 ext/standard/basic_functions.c 中:
PHP_FUNCTION(ini_set)
{
……
// 在EG(ini_directives) 中查找要修改的配置项
old_value = zend_ini_string(ZSTR_VAL(varname), (int)ZSTR_LEN(varname), 0);
……
// 动态修改配置
if (zend_alter_ini_entry_ex(varname, new_value, PHP_INI_USER, PHP_INI_STAGE_
RUNTIME, 0) == FAILURE) {
zval_dtor(return_value);
RETURN_FALSE;
}
}
函数主要有 3 个阶段。
-
到已经解析完成的配置 EG(ini_directives) 中获取要修改的配置项的当前值。这里注意,如果要修改的配置项不在 EG(ini_directives) 中,则无法通过这个函数来动态添加配置项。
-
判断在开启 open_basedir 的情况下,要修改的选项是否在这个配置项允许的范围内。
-
修改配置:修改配置的函数 zend_alter_ini_entry_ex 有如下几个阶段。
阶段一:检查配置模式。前文已经介绍 PHP 的配置项有 4 种模式。在 ini_set 函数中只能修改 PHP_INI_USER 模式的配置项,在 zend_alter_ini_entry_ex 这个函数中,可做这个校验。如果不是模式的配置项,会直接返回 False。判断的方式非常简单,代码如下:
// modify_type在这个函数中被强制设置为PHP_INI_USER
if (! (ini_entry->modifiable & modify_type)) {
return FAILURE;
}
阶段二:判断是否已经修改过当前配置项。在 CLI 模式下,PHP 每次运行都会解析配置文件,在 PHP-FPM 模式下,则只会解析一次。如果通过 ini_set 函数动态修改配置,会检测 EG(modified_ini_directives) 中是否有这个配置项。这是一个 hashtable 结构,用来保存修改过的配置项。当第一次修改配置,会初始化 EG(modified_ini_directives),然后把当前配置项的 modified 标记为 1,表示已经被修改过,配置项的原始值被赋值到 orig_value,当前配置项保存到 EG(modified_ini_directives) 中。当调用 ini_restore 函数或者是请求结束,会把保存起来的原始值再恢复到 EG(ini_directives) 中。这个检测只会做一次,如果多次修改同一个配置项,EG(modified_ini_directives) 保存的配置项的 orig_value 一定是最原始的配置值。
阶段三:使配置生效。部分配置项需要映射到某个扩展内的全局变量或者是其他某个结构。通过 ini_set 函数修改配置项时,如果检测到配置项有 on_modify 回调函数,也会同步调用 on_modify,使新的配置立即生效。
到这里,动态修改配置就介绍完了,有兴趣的读者可以跟踪一下请求结束的时候是怎么恢复原始配置的,这里就不再展开了。其主要实现在 Zend/zend_ini.c 中:
ZEND_API int zend_ini_deactivate(void) /* {{{ */
{
if (EG(modified_ini_directives)) {
zend_hash_apply(EG(modified_ini_directives), zend_restore_ini_entry_wrapper);
zend_hash_destroy(EG(modified_ini_directives));
FREE_HASHTABLE(EG(modified_ini_directives));
EG(modified_ini_directives) = NULL;
}
return SUCCESS;
}