php.ini是PHP主要的配置文件,解析时PHP将在这些地方依次查找该文件:当前工作目录、环境变量PHPRC指定目录、编译时指定的路径,在命令行模式下,php.ini的查找路径可以用-c
参数替代。
该文件的语法非常简单:配置标识符 = 值
。空白字符和用分号';'开始的行被忽略,[xxx]行也被忽略;配置标识符大写敏感,通常会用'.'区分不同的节;值可以是数字、字符串、PHP常量、位运算表达式。
关于php.ini的解析过程本节不作介绍,只从应用的角度介绍如何在一个扩展中获取一个配置项,通常会把php.ini的配置映射到一个变量,从而在使用时直接读取那个变量,也就是把所有的配置转化为了C语言中的变量,扩展中一般会把php.ini配置映射到上一节介绍的全局变量(资源),要想实现这个转化需要在扩展中为每一项配置设置映射规则:
PHP_INI_BEGIN()
//每一项配置规则
...
PHP_INI_END();
这两个宏实际只是把各配置规则组成一个数组,配置规则通过STD_PHP_INI_ENTRY()
设置:
STD_PHP_INI_ENTRY(name,default_value,modifiable,on_modify,property_name,struct_type,struct_ptr)
> 除了STD_PHP_INI_ENTRY()这个宏还有一个类似的宏STD_PHP_INI_BOOLEAN()
,用法一致,差别在于后者会自动把配置添加到phpinfo()输出中。
这个宏展开后生成一个zend_ini_entry_def
结构:
typedef struct _zend_ini_entry_def {
const char *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; //映射成员所在结构体的偏移:offsetof(type, member-designator)取到
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;
比如将php.ini中的mytest.opene_cache
值映射到MYTEST_G()
结构中的open_cache,类型为zend_long,默认值109,则可以这么定义:
PHP_INI_BEGIN()
STD_PHP_INI_ENTRY("mytest.open_cache", "109", PHP_INI_ALL, OnUpdateLong, open_cache, zend_mytest_globals, mytest_globals)
PHP_INI_END();
property_name设置的是要映射到的结构成员mytest_globals->open_cache
,zend_mytest_globals、mytest_globals都是宏展开后的实际值,前者是结构体类型,后者是具体分配的变量,上面的定义展开后:
static const zend_ini_entry_def ini_entries[] = {
{
"mytest.open_cache",
OnUpdateLong,
(void *) XtOffsetOf(zend_mytest_globals, open_cache), //获取成员在结构体中的内存偏移
(void*)&mytest_globals,
NULL,
"109",
NULL,
PHP_INI_ALL,
sizeof("mytest.open_cache")-1,
sizeof("109")-1
},
{ NULL, NULL, NULL, NULL, NULL, NULL, NULL, 0, 0, 0}
}
> XtOffsetOf()
这个宏在linux环境下展开就是offsetof()
,用来获取一个结构体成员的offset,比如:
>
> #include
> #include
>
> typedef struct{
> int id;
> char name;
> }my_struct;
>
> int main(void)
> {
> printf("%d\n", (void)offsetof(my_struct, name));
> return 0;
> }
>
> 通过这个offset及结构体指针就可以读取这个成员:(char*)my_sutct + offset
,等价于my_sutct->name
。
定义完上面的配置映射规则后就可以进行映射了,这一步通过REGISTER_INI_ENTRIES()
完成,这个宏展开后:zend_register_ini_entries(ini_entries, module_number)
,ini_entries是PHP_INI_BEGIN/END()
两个宏生成的配置映射规则数组,通常会把这个操作放到PHP_MINIT_FUNCTION()
中,注意:此时php.ini已经解析到configuration_hash
哈希表中,zend_register_ini_entries()
将根据配置name查找这个哈希表,如果找到了表明用户在php.ini中配置了该项,然后将调用此规则指定的on_modify函数进行赋值,比如上面的示例将调用OnUpdateLong()
处理,整体的流程:
ZEND_API int zend_register_ini_entries(const zend_ini_entry_def *ini_entry, int module_number)
{
zend_ini_entry *p;
zval *default_value;
HashTable *directives = registered_zend_ini_directives;
while (ini_entry->name) {
//分配zend_ini_entry结构
p = pemalloc(sizeof(zend_ini_entry), 1);
//zend_ini_entry初始化
...
//添加到registered_zend_ini_directives,EG(ini_directives)也是指向此HashTable
if (zend_hash_add_ptr(directives, p->name, (void*)p) == NULL) {
...
}
//zend_get_configuration_directive()最终将调用cfg_get_entry()
//从configuration_hash哈希表中查找配置,如果没有找到将使用默认值
default_value = zend_get_configuration_directive(p->name)
...
if (p->on_modify) {
//调用定义的赋值handler处理
p->on_modify(p, p->value, p->mh_arg1, p->mh_arg2, p->mh_arg3, ZEND_INI_STAGE_STARTUP);
}
}
}
OnUpdateLong()
赋值处理:
ZEND_API ZEND_INI_MH(OnUpdateLong)
{
zend_long *p;
#ifndef ZTS
//存储结构的指针
char *base = (char *) mh_arg2;
#else
char *base;
//ZTS下需要向TSRM中获取存储结构的指针
base = (char *) ts_resource(*((int *) mh_arg2));
#endif
//指向结构体成员的位置
p = (zend_long *) (base+(size_t) mh_arg1);
//将值转为zend_long
*p = zend_atol(ZSTR_VAL(new_value), (int)ZSTR_LEN(new_value));
return SUCCESS;
}
如果PHP提供的几个on_modify不能满足需求可以自定义on_modify函数,举个例子:将php.ini中的配置mytest.class
插入MYTESY_G(class_table)哈希表,则可以在扩展中定义这样一个on_modify:ZEND_INI_MH(OnUpdateAddArray)
,将php.ini映射到全局变量的完整代码:
//php_mytest.h
#define MYTEST_G(v) ZEND_MODULE_GLOBALS_ACCESSOR(mytest, v)
ZEND_BEGIN_MODULE_GLOBALS(mytest)
zend_long open_cache;
HashTable class_table;
ZEND_END_MODULE_GLOBALS(mytest)
//自定义on_modify函数
ZEND_API ZEND_INI_MH(OnUpdateAddArray);
//mytest.c
ZEND_DECLARE_MODULE_GLOBALS(mytest)
PHP_INI_BEGIN()
STD_PHP_INI_ENTRY("mytest.open_cache", "109", PHP_INI_ALL, OnUpdateLong, open_cache, zend_mytest_globals, mytest_globals)
STD_PHP_INI_ENTRY("mytest.class", "stdClass", PHP_INI_ALL, OnUpdateAddArray, class_table, zend_mytest_globals, mytest_globals)
PHP_INI_END();
ZEND_API ZEND_INI_MH(OnUpdateAddArray)
{
HashTable *ht;
zval val;
#ifndef ZTS
char *base = (char *) mh_arg2;
#else
char *base;
base = (char *) ts_resource(*((int *) mh_arg2));
#endif
ht = (HashTable*)(base+(size_t) mh_arg1);
ZVAL_NULL(&val);
zend_hash_add(ht, new_value, &val);
}
PHP_MINIT_FUNCTION(mytest)
{
zend_hash_init(&MYTEST_G(class_table), 0, NULL, NULL, 1);
//将php.ini解析到指定结构体
REGISTER_INI_ENTRIES();
printf("open_cache %d\n", MYTEST_G(open_cache));
}
zend_module_entry mytest_module_entry = {
STANDARD_MODULE_HEADER,
"mytest",
NULL,//mytest_functions,
PHP_MINIT(mytest),
NULL,//PHP_MSHUTDOWN(mytest),
NULL,//PHP_RINIT(mytest),
NULL,//PHP_RSHUTDOWN(mytest),
NULL,//PHP_MINFO(mytest),
"1.0.0",
STANDARD_MODULE_PROPERTIES
};
#ifdef COMPILE_DL_TIMEOUT
#ifdef ZTS
ZEND_TSRMLS_CACHE_DEFINE()
#endif
ZEND_GET_MODULE(mytest)
#endif
本节主要介绍了如何将php.ini配置项解析到C语言变量中,总结下主要分为两步: