扩展配置
前文介绍了配置文件的解析,接下来介绍配置项是如何生效的。在实际的项目开发中,我们可能会用到各种扩展,如 date、mbstring、mysqli 等,这些扩展其实也有着属于自己的配置项,例如,php.ini 中的 date section 部分就是 date 模块的配置:
[Date]
;date.timezone = Asia/Shanghai
;date.default_latitude = 31.7667
;date.default_longitude = 35.2333
;date.sunrise_zenith = 90.583333
;date.sunset_zenith = 90.583333
在配置文件的解析过程中,会把配置文件中的 PHP 扩展和 Zend 扩展解析出来放在名为 extension_lists 的结构中以供后续加载。
typedef struct _php_extension_lists {
zend_llist engine; // 保存PHP extension
zend_llist functions; // 保存Zend extension
} php_extension_lists;
在配置文件解析完成以后,开始加载 PHP 核心默认配置项(display_errors、post_max_size 等)和 Zend 默认项(error_reporting、zend.enable_gc 等)。其解析的结果保存在名为 registered_zend_ini_directives 的全局 hashtable 中,EG(ini_directives) 也指向该 hashtable。核心配置的解析和扩展配置的解析非常类似,我们将在后面展开介绍。在核心的默认配置解析完成后,开始加载静态编译的扩展和保存到 extension_lists 中的动态扩展。接着依次启动各个扩展:
/* Register PHP core ini entries */
REGISTER_INI_ENTRIES();
/* Register Zend ini entries */
zend_register_standard_ini_entries();
/* startup extensions statically compiled in /
php_register_internal_extensions_func();
/* start additional PHP extensions */
php_register_extensions_bc(additional_modules, num_additional_modules);
php_ini_register_extensions();
核心配置以及其他扩展的默认配置项都是硬编码在对应的源代码中。PHP 扩展通过 PHP_INI_BEGIN 和 PHP_INI_END 宏来定义当前模块的配置(Zend 扩展通过 ZEND_INI_BEGIN 和 ZEND_INI_END 来定义)。我们以 date 扩展来说明扩展配置是怎么解析的。
date 扩展配置的定义在 ext/date/php_date.c中,如下所示:
PHP_INI_BEGIN()
STD_PHP_INI_ENTRY("date.timezone", "", PHP_INI_ALL, OnUpdate_date_timezone,
default_timezone, zend_date_globals, date_globals)
PHP_INI_ENTRY("date.default_latitude", DATE_DEFAULT_LATITUDE, PHP_INI_ALL,
NULL)
PHP_INI_ENTRY("date.default_longitude", DATE_DEFAULT_LONGITUDE, PHP_INI_ALL,
NULL)
PHP_INI_ENTRY("date.sunset_zenith", DATE_SUNSET_ZENITH, PHP_INI_ALL, NULL)
PHP_INI_ENTRY("date.sunrise_zenith", DATE_SUNRISE_ZENITH, PHP_INI_ALL, NULL)
PHP_INI_END()
可以看出 date 扩展一共注册了 5 个配置项。上边的宏展开后其实只是把包围在 PHP_INI_BEGIN 和 PHP_INI_END 之间的配置项组成一个名为 ini_entries 的 zend_ini_entry_def 结构的数组。配置项有以下几种注册方式:
-
ZEND_INI_ENTRY (name, default_value, modifiable, on_modify);
-
ZEND_INI_ENTRY_EX (name, default_value, modifiable, on_modify,NULL);
-
STD_ZEND_INI_ENTRY (name, default_value, modifiable, on_modify,property_name, struct_type, struct_ptr);
-
STD_ZEND_INI_BOOLEAN (name, default_value, modifiable, on_modify,property_name, struct_type, struct_ptr);
-
STD_ZEND_INI_ENTRY_EX (name, default_value, modifiable, on_modify,property_name, struct_type, struct_ptr, displayer);
可能读者会感到疑惑:上文的定义明明是 PHP_INI 开头,这里怎么变成了 ZEND_INI 呢?其实只是一种名称替换:
#define PHP_INI_ENTRY ZEND_INI_ENTRY
所有的配置宏展开后,最终如下所示:
{ name, on_modify, arg1, arg2, arg3, default_value, displayer, modifiable, sizeof(name)-1, sizeof(default_value)-1 }
每一个默认配置项实质上是 _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;
以 date 扩展的第一个配置项为例,实例化后的结构体为
typedef struct _zend_ini_entry_def {
"date.timezone",
OnUpdate_date_timezone,
(void *) XtOffsetOf(zend_date_globals, default_timezone),
(void *) &date_globals,
NULL,
"",
NULL,
PHP_INI_ALL,
sizeof("date.timezone")-1,
sizeof("")-1
} zend_ini_entry_def;
配置文件中的 “date.timezone” 配置映射到 zend_date_globals 类型的变量 date_globals 中的 default_timezone。其默认值为空,可修改范围为 PHP_INI_ALL。当有自定义配置时,调用 OnUpdate_date_timezone 函数来处理。其他配置项类似。
不是所有的配置项都一定映射到某个结构中,有些配置项只会保存在 EG(ini_directives) 中。 |
PHP 扩展的启动是依次执行各个扩展中的 PHP_MINIT_FUNCTION,在每个扩展启动期间会执行 REGISTER_INI_ENTRIES。展开后如下:
/*
* Registration / unregistration
*/
ZEND_API int zend_register_ini_entries(const zend_ini_entry_def *ini_entry, int
module_number) /* {{{ */
{
// code
}
PHP_INI_BEGIN 和 PHP_INI_END 定义的配置项数组会保存到 ini_entries, ini_entries-zend_register_ini_entries 的参数 ini_entry 即为 ini_entries 的元素;ini_entrieszend_register_ini_entries 的第二个参数 module_number 为 module_registry 数组中的编号,module_registry 为所有已经加载的扩展数组。zend_register_ini_entries 会依次把扩展中的各个配置项添加到全局配置 registered_zend_ini_directives 中。添加后新的配置项变为如下结构:
struct _zend_ini_entry {
zend_string *name;
ZEND_INI_MH((*on_modify));
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; // 配置所在的模块注册的编号
};
配置项添加到 registered_zend_ini_directives 的同时,根据配置项的 key 到 configuration_hash 中查找,如果能找到,说明有自定义的配置项,没有则继续使用默认值。最后调用该配置项的 on_modify 回调函数,使新的配置项生效。例如,date 扩展的 timezone 配置项,其 on_modify 回调函数如下:
static PHP_INI_MH(OnUpdate_date_timezone){}
这个函数会把自定义的时区配置复制到当前模块,同时做时区配置的合法性检查。不同的配置可以定义自己的回调函数来做个性化处理。在这里读者可能有疑问,为什么要把配置项复制到当前模块,直接从 registered_zend_ini_directives 中获取不行么?PHP 的扩展有一个当前模块级别的 global 结构体,可以保存当前扩展的一些全局信息(注意:不只是保存配置),例如:
ZEND_BEGIN_MODULE_GLOBALS(date)
char *default_timezone;
char *timezone;
HashTable *tzcache;
timelib_error_container *last_errors;
int timezone_valid;
ZEND_END_MODULE_GLOBALS(date)
这个宏展开即为
typedef struct _zend_date_globals {
char *default_timezone;
char *timezone;
HashTable *tzcache;
timelib_error_container *last_errors;
int timezone_valid;
} zend_date_globals;
模块在启动完毕后,相关的配置及模块内的全局信息会保存在这个结构中,方便模块内部处理。各个模块还可以通过宏 ZEND_MODULE_GLOBALS_ACCESSOR 来定义配置的快捷访问方式,例如:
#define DATEG(v) ZEND_MODULE_GLOBALS_ACCESSOR(date, v)
通过这样的方式,在之后的执行阶段,date扩展不需访问 registered_zend_ini_directives。扩展内通过 DATEG(v) 来访问自己的 date_globals,便可以拿到用户自定义的配置。图8-2为 date 模块配置解析示意图。

到这里,date 模块的配置解析过程就介绍完了,其他模块的解析过程类似,在这里不再一一展开,有兴趣的读者可以追踪一下其他模块的解析过程。