php中的ini配置原理詳解
使用php的同學(xué)都知道php.ini配置的生效會(huì)貫穿整個(gè)SAPI的生命周期。在一段php腳本的執(zhí)行過(guò)程中,如果手動(dòng)修改ini配置,是不會(huì)啟作用的。此時(shí)如果無(wú)法重啟apache或者nginx等,那么就只能顯式的在php代碼中調(diào)用ini_set接口。ini_set是php向我們提供的一個(gè)動(dòng)態(tài)修改配置的函數(shù),需要注意的是,利用ini_set所設(shè)置的配置與ini文件中設(shè)置的配置,其生效的時(shí)間范圍并不相同。在php腳本執(zhí)行結(jié)束之后,ini_set的設(shè)置便會(huì)隨即失效。
因此本文打算分兩篇,第一篇闡述php.ini配置原理,第二篇講動(dòng)態(tài)修改php配置。
php.ini的配置大致會(huì)涉及到三塊數(shù)據(jù),configuration_hash,EG(ini_directives)以及PG、BG、PCRE_G、JSON_G、XXX_G等。如果不清楚這三種數(shù)據(jù)的含義也沒(méi)有關(guān)系,下文會(huì)詳細(xì)解釋。
1,解析INI配置文件
由于php.ini需要在SAPI過(guò)程中一直生效,那么解析ini文件并據(jù)此來(lái)構(gòu)建php配置的工作,必定是發(fā)生SAPI的一開(kāi)始。換句話說(shuō),也就是必定發(fā)生在php的啟動(dòng)過(guò)程中。php需要任意一個(gè)實(shí)際的請(qǐng)求到達(dá)之前,其內(nèi)部已經(jīng)生成好這些配置。
反映到php的內(nèi)核,即為php_module_startup函數(shù)。
php_module_startup主要負(fù)責(zé)對(duì)php進(jìn)行啟動(dòng),通常它會(huì)在SAPI開(kāi)始的時(shí)候被調(diào)用。btw,還有一個(gè)常見(jiàn)的函數(shù)是php_request_startup,它負(fù)責(zé)將在每個(gè)請(qǐng)求到來(lái)的時(shí)刻進(jìn)行初始化,php_module_startup與php_request_startup是兩個(gè)標(biāo)識(shí)性的動(dòng)作,不過(guò)對(duì)他們進(jìn)行分析并不在本文的探討范圍內(nèi)。
舉個(gè)例子,當(dāng)php掛接在apache下面做一個(gè)module,那么apache啟動(dòng)的時(shí)候,便會(huì)激活所有這些module,其中包括php module。在激活php module時(shí),便會(huì)調(diào)用到php_module_startup。php_module_startup函數(shù)完成了茫茫多的工作,一旦php_module_startup調(diào)用結(jié)束就意味著,OK,php已經(jīng)啟動(dòng),現(xiàn)在可以接受請(qǐng)求并作出響應(yīng)了。
在php_module_startup函數(shù)中,與解析ini文件相關(guān)的實(shí)現(xiàn)是:
/* this will read in php.ini, set up the configuration parameters,
load zend extensions and register php function extensions
to be loaded later */
if (php_init_config(TSRMLS_C) == FAILURE) {
return FAILURE;
}
可以看到,其實(shí)就是調(diào)用了php_init_config函數(shù),去完成對(duì)ini文件的parse。parse工作主要進(jìn)行l(wèi)ex&grammar分析,并將ini文件中的key、value鍵值對(duì)提取出來(lái)并保存。php.ini的格式很簡(jiǎn)單,等號(hào)左側(cè)為key,右側(cè)為value。每當(dāng)一對(duì)kv被提取出來(lái)之后,php將它們存儲(chǔ)到哪兒呢?答案就是之前提到的configuration_hash。
static HashTable configuration_hash;
configuration_hash聲明在php_ini.c中,它是一個(gè)HashTable類型的數(shù)據(jù)結(jié)構(gòu)。顧名思義,其實(shí)就是張hash表。題外話,在php5.3之前的版本是沒(méi)法獲取configuration_hash的,因?yàn)樗莗hp_ini.c文件的一個(gè)static的變量。后來(lái)php5.3添加了php_ini_get_configuration_hash接口,該接口直接返回&configuration_hash,使 得php各個(gè)擴(kuò)展可以方便的一窺configuration_hash全貌...真是普大喜奔...
注意四點(diǎn):
第一,php_init_config不會(huì)做除了詞法語(yǔ)法以外的任何校驗(yàn)。也就是說(shuō),假如我們?cè)趇ni文件中添加一行 hello=world,只要這是一個(gè)格式正確的配置項(xiàng),那么最終configuration_hash中就會(huì)包含一個(gè)鍵為hello、值為world的元素,configuration_hash最大限度的反映出ini文件。
第二,ini文件允許我們以數(shù)組的形式進(jìn)行配置。例如ini文件中寫(xiě)入以下三行:
drift.arr[]=1
drift.arr[]=2
drift.arr[]=3
那么最終生成的configuration_hash表中,就會(huì)存在一個(gè)key為drift.arr的元素,其value為一個(gè)包含的1,2,3三個(gè)數(shù)字的數(shù)組。這是一種極為罕見(jiàn)的配置方法。
第三,php還允許我們除了默認(rèn)的php.ini文件(準(zhǔn)確說(shuō)是php-%s.ini)之外,另外構(gòu)建一些ini文件。這些ini文件會(huì)被放入一個(gè)額外的目錄。該目錄由環(huán)境變量PHP_INI_SCAN_DIR來(lái)指定,當(dāng)php_init_config解析完了php.ini之后,會(huì)再次掃描此目錄,然后找出目錄中所有.ini文件來(lái)分析。這些額外的ini文件中產(chǎn)生的kv鍵值對(duì),也會(huì)被加入到configuration_hash中去。
這是一個(gè)偶爾有用的特性,假設(shè)我們自己開(kāi)發(fā)php的擴(kuò)展,卻又不想將配置混入php.ini,便可以另外寫(xiě)一份ini,并通過(guò)PHP_INI_SCAN_DIR告訴php該去哪兒找到它。當(dāng)然,其缺點(diǎn)也顯而易見(jiàn),其需要設(shè)置額外的環(huán)境變量來(lái)支持。更好的解決辦法是,開(kāi)發(fā)者在擴(kuò)展中自己調(diào)用php_parse_user_ini_file或zend_parse_ini_file去解析對(duì)應(yīng)的ini文件。
第四,在configuration_hash中,key是字符串,那么值的類型是什么?答案也是字符串(除了上述很特殊的數(shù)組)。具體來(lái)說(shuō),比如下面的配置:
display_errors = On
log_errors = Off
log_errors_max_len = 1024
那么最后configuration_hash中實(shí)際存放的鍵值對(duì)為:
key: "display_errors"
val : "1"
key: "log_errors"
val : ""
key: "log_errors_max_len"
val : "1024"
注意log_errors,其存放的值連"0"都不是,就是一個(gè)實(shí)實(shí)在在地空字符串。另外,log_errors_max_len也并非數(shù)字,而是字符串1024。
分析至此,基本上解析ini文件相關(guān)的內(nèi)容都說(shuō)清楚了。簡(jiǎn)單總結(jié)一下:
1,解析ini發(fā)生在php_module_startup階段
2,解析結(jié)果存放在configuration_hash里。
2,配置作用到模塊
php的大致結(jié)構(gòu)可以看成是最下層有一個(gè)zend引擎,它負(fù)責(zé)與OS進(jìn)行交互、編譯php代碼、提供內(nèi)存托管等等,在zend引擎的上層,排列著很多很多的模塊。其中最核心的就一個(gè)Core模塊,其他還有比如Standard,PCRE,Date,Session等等...這些模塊還有另一個(gè)名字叫php擴(kuò)展。我們可以簡(jiǎn)單理解為,每個(gè)模塊都會(huì)提供一組功能接口給開(kāi)發(fā)者來(lái)調(diào)用,舉例來(lái)說(shuō),常用的諸如explode,trim,array等內(nèi)置函數(shù),便是由Standard模塊提供的。
為什么需要談到這些,是因?yàn)樵趐hp.ini里除了針對(duì)php自身,也就是針對(duì)Core模塊的一些配置(例如safe_mode,display_errors,max_execution_time等),還有相當(dāng)多的配置是針對(duì)其他不同模塊的。
例如,date模塊,它提供了常見(jiàn)的date, time,strtotime等函數(shù)。在php.ini中,它的相關(guān)配置形如:
[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
除了這些模塊擁有獨(dú)立的配置,zend引擎也是可配的,只不過(guò)zend引擎的可配項(xiàng)非常少,只有error_reporting,zend.enable_gc和detect_unicode三項(xiàng)。
在上一小節(jié)中我們已經(jīng)談到,php_module_startup會(huì)調(diào)用php_init_config,其目的是解析ini文件并生成configuration_hash。那么接下來(lái)在php_module_startup中還會(huì)做什么事情呢?很顯然,就是會(huì)將configuration_hash中的配置作用于Zend,Core,Standard,SPL等不同模塊。當(dāng)然這并非一個(gè)一蹴而就的過(guò)程,因?yàn)閜hp通常會(huì)包含有很多模塊,php啟動(dòng)的過(guò)程中這些模塊也會(huì)依次進(jìn)行啟動(dòng)。那么,對(duì)模塊A進(jìn)行配置的過(guò)程,便是發(fā)生在模塊A的啟動(dòng)過(guò)程中。
有擴(kuò)展開(kāi)發(fā)經(jīng)驗(yàn)的同學(xué)會(huì)直接指出,模塊A的啟動(dòng)不就是在PHP_MINIT_FUNCTION(A)中么?
是的,如果模塊A需要配置,那么在PHP_MINIT_FUNCTION中,可以調(diào)用REGISTER_INI_ENTRIES()來(lái)完成。REGISTER_INI_ENTRIES會(huì)根據(jù)當(dāng)前模塊所需要的配置項(xiàng)名稱,去configuration_hash查找用戶設(shè)置的配置值,并更新到模塊自己的全局空間中。
2.1,模塊的全局空間
要理解如何將ini配置從configuration_hash作用到各個(gè)模塊之前,有必要先了解一下php模塊的全局空間。對(duì)于不同的php模塊,均可以開(kāi)辟一塊屬于自己的存儲(chǔ)空間,并且這塊空間對(duì)于該模塊來(lái)說(shuō),是全局可見(jiàn)的。一般而言,它會(huì)被用來(lái)存放該模塊所需的ini配置。也就是說(shuō),configuration_hash中的配置項(xiàng),最終會(huì)被存放到該全局空間中。在模塊的執(zhí)行過(guò)程中,只需要直接訪問(wèn)這塊全局空間,就可以拿到用戶針對(duì)該模塊進(jìn)行的設(shè)置。當(dāng)然,它也經(jīng)常被用來(lái)記錄模塊在執(zhí)行過(guò)程中的中間數(shù)據(jù)。
我們以bcmath模塊來(lái)舉例說(shuō)明,bcmath是一個(gè)提供數(shù)學(xué)計(jì)算方面接口的php模塊,首先我們來(lái)看看它有哪些ini配置:
PHP_INI_BEGIN()
STD_PHP_INI_ENTRY("bcmath.scale", "0", PHP_INI_ALL, OnUpdateLongGEZero, bc_precision, zend_bcmath_globals, bcmath_globals)
PHP_INI_END()
bcmath只有一個(gè)配置項(xiàng),我們可以在php.ini中用bcmath.scale來(lái)配置bcmath模塊。
接下來(lái)繼續(xù)看看bcmatch模塊的全局空間定義。在php_bcmath.h中有如下聲明:
ZEND_BEGIN_MODULE_GLOBALS(bcmath)
bc_num _zero_;
bc_num _one_;
bc_num _two_;
long bc_precision;
ZEND_END_MODULE_GLOBALS(bcmath)
宏展開(kāi)之后,即為:
typedef struct _zend_bcmath_globals {
bc_num _zero_;
bc_num _one_;
bc_num _two_;
long bc_precision;
} zend_bcmath_globals;
其實(shí),zend_bcmath_globals類型就是bcmath模塊中的全局空間類型。這里僅僅聲明了zend_bcmath_globals結(jié)構(gòu)體,在bcmath.c中還有具體的實(shí)例化定義:
// 展開(kāi)后即為zend_bcmath_globals bcmath_globals;
ZEND_DECLARE_MODULE_GLOBALS(bcmath)
可以看出,用ZEND_DECLARE_MODULE_GLOBALS完成了對(duì)變量bcmath_globals的定義。
bcmath_globals是一塊真正的全局空間,它包含有四個(gè)字段。其最后一個(gè)字段bc_precision,對(duì)應(yīng)于ini配置中的bcmath.scale。我們?cè)趐hp.ini中設(shè)置了bcmath.scale的值,隨后在啟動(dòng)bcmath模塊的時(shí)候,bcmath.scale的值被更新到bcmath_globals.bc_precision中去。
把configuration_hash中的值,更新到各個(gè)模塊自己定義的xxx_globals變量中,就是所謂的將ini配置作用到模塊。一旦模塊啟動(dòng)完成,那么這些配置也都作用到位。所以在隨后的執(zhí)行階段,php模塊無(wú)需再次訪問(wèn)configuration_hash,模塊僅需要訪問(wèn)自己的XXX_globals,就可以拿到用戶設(shè)定的配置。
bcmath_globals,除了有一個(gè)字段為ini配置項(xiàng),其他還有三個(gè)字段為何意?這就是模塊全局空間的第二個(gè)作用,它除了用于ini配置,還可以存儲(chǔ)模塊執(zhí)行過(guò)程中的一些數(shù)據(jù)。
再例如json模塊,也是php中一個(gè)很常用的模塊:
ZEND_BEGIN_MODULE_GLOBALS(json)
int error_code;
ZEND_END_MODULE_GLOBALS(json)
可以看到j(luò)son模塊并不需要ini配置,它的全局空間只有一個(gè)字段error_code。error_code記錄了上一次執(zhí)行json_decode或者json_encode中發(fā)生的錯(cuò)誤。json_last_error函數(shù)便是返回這個(gè)error_code,來(lái)幫助用戶定位錯(cuò)誤原因。
為了能夠很便捷的訪問(wèn)模塊全局空間變量,php約定俗成的提出了一些宏。比如我們想訪問(wèn)json_globals中的error_code,當(dāng)然可以直接寫(xiě)做json_globals.error_code(多線程環(huán)境下不行),不過(guò)更通用的寫(xiě)法是定義JSON_G宏:
#define JSON_G(v) (json_globals.v)
我們使用JSON_G(error_code)來(lái)訪問(wèn)json_globals.error_code。本文剛開(kāi)始的時(shí)候,曾提到PG、BG、JSON_G、PCRE_G,XXX_G等等,這些宏在php源代碼中也是很常見(jiàn)的?,F(xiàn)在我們可以很輕松的理解它們,PG宏可以訪問(wèn)Core模塊的全局變量,BG訪問(wèn)Standard模塊的全局變量,PCRE_G則訪問(wèn)PCRE模塊的全局變量。
#define PG(v) (core_globals.v)
#define BG(v) (basic_globals.v)
2.2,如何確定一個(gè)模塊需要哪些配置呢?
模塊需要什么樣的INI配置,都是在各個(gè)模塊中自己定義的。舉例來(lái)說(shuō),對(duì)于Core模塊,有如下的配置項(xiàng)定義:
PHP_INI_BEGIN()
......
STD_PHP_INI_ENTRY_EX("display_errors", "1", PHP_INI_ALL, OnUpdateDisplayErrors, display_errors, php_core_globals, core_globals, display_errors_mode)
STD_PHP_INI_BOOLEAN("enable_dl", "1", PHP_INI_SYSTEM, OnUpdateBool, enable_dl, php_core_globals, core_globals)
STD_PHP_INI_BOOLEAN("expose_php", "1", PHP_INI_SYSTEM, OnUpdateBool, expose_php, php_core_globals, core_globals)
STD_PHP_INI_BOOLEAN("safe_mode", "0", PHP_INI_SYSTEM, OnUpdateBool, safe_mode, php_core_globals, core_globals)
......
PHP_INI_END()
可以在php-src\main\main.c文件大概450+行找到上述代碼。其中涉及的宏比較多,有ZEND_INI_BEGIN 、ZEND_INI_END、PHP_INI_ENTRY_EX、STD_PHP_INI_BOOLEAN等等,本文不一一贅述,感興趣的讀者可自行分析。
上述代碼進(jìn)行宏展開(kāi)后得到:
static const zend_ini_entry ini_entries[] = {
..
{ 0, PHP_INI_ALL, "display_errors",sizeof("display_errors"),OnUpdateDisplayErrors,(void *)XtOffsetOf(php_core_globals, display_errors), (void *)&core_globals, NULL, "1", sizeof("1")-1, NULL, 0, 0, 0, display_errors_mode },
{ 0, PHP_INI_SYSTEM, "enable_dl", sizeof("enable_dl"), OnUpdateBool, (void *)XtOffsetOf(php_core_globals, enable_dl), (void *)&core_globals, NULL, "1", sizeof("1")-1, NULL, 0, 0, 0, zend_ini_boolean_displayer_cb },
{ 0, PHP_INI_SYSTEM, "expose_php", sizeof("expose_php"), OnUpdateBool, (void *)XtOffsetOf(php_core_globals, expose_php), (void *)&core_globals, NULL, "1", sizeof("1")-1, NULL, 0, 0, 0, zend_ini_boolean_displayer_cb },
{ 0, PHP_INI_SYSTEM, "safe_mode", sizeof("safe_mode"), OnUpdateBool, (void *)XtOffsetOf(php_core_globals, safe_mode), (void *)&core_globals, NULL, "0", sizeof("0")-1, NULL, 0, 0, 0, zend_ini_boolean_displayer_cb },
...
{ 0, 0, NULL, 0, NULL, NULL, NULL, NULL, NULL, 0, NULL, 0, 0, 0, NULL }
};
我們看到,配置項(xiàng)的定義,其本質(zhì)上就是定義了一個(gè)zend_ini_entry類型的數(shù)組。zend_ini_entry結(jié)構(gòu)體的字段具體含義為:
struct _zend_ini_entry {
int module_number; // 模塊的id
int modifiable; // 可被修改的范圍,例如php.ini,ini_set
char *name; // 配置項(xiàng)的名稱
uint name_length;
ZEND_INI_MH((*on_modify)); // 回調(diào)函數(shù),配置項(xiàng)注冊(cè)或修改的時(shí)候會(huì)調(diào)用
void *mh_arg1; // 通常為配置項(xiàng)字段在XXX_G中的偏移量
void *mh_arg2; // 通常為XXX_G
void *mh_arg3; // 通常為保留字段,極少用到
char *value; // 配置項(xiàng)的值
uint value_length;
char *orig_value; // 配置項(xiàng)的原始值
uint orig_value_length;
int orig_modifiable; // 配置項(xiàng)的原始modifiable
int modified; // 是否發(fā)生過(guò)修改,如果有修改,則orig_value會(huì)保存修改前的值
void (*displayer)(zend_ini_entry *ini_entry, int type);
};
2.3,將配置作用到模塊——REGISTER_INI_ENTRIES
經(jīng)常能夠在不同擴(kuò)展的PHP_MINIT_FUNCTION里看到REGISTER_INI_ENTRIES。REGISTER_INI_ENTRIES主要負(fù)責(zé)完成兩件事情,第一,對(duì)模塊的全局空間XXX_G進(jìn)行填充,同步configuration_hash中的值到XXX_G中去。其次,它還生成了EG(ini_directives)。
REGISTER_INI_ENTRIES也是一個(gè)宏,展開(kāi)之后實(shí)則為zend_register_ini_entries方法。具體來(lái)看下zend_register_ini_entries的實(shí)現(xiàn):
ZEND_API int zend_register_ini_entries(const zend_ini_entry *ini_entry, int module_number TSRMLS_DC) /* {{{ */
{
// ini_entry為zend_ini_entry類型數(shù)組,p為數(shù)組中每一項(xiàng)的指針
const zend_ini_entry *p = ini_entry;
zend_ini_entry *hashed_ini_entry;
zval default_value;
// EG(ini_directives)就是registered_zend_ini_directives
HashTable *directives = registered_zend_ini_directives;
zend_bool config_directive_success = 0;
// 還記得ini_entry最后一項(xiàng)固定為{ 0, 0, NULL, ... }么
while (p->name) {
config_directive_success = 0;
// 將p指向的zend_ini_entry加入EG(ini_directives)
if (zend_hash_add(directives, p->name, p->name_length, (void*)p, sizeof(zend_ini_entry), (void **) &hashed_ini_entry) == FAILURE) {
zend_unregister_ini_entries(module_number TSRMLS_CC);
return FAILURE;
}
hashed_ini_entry->module_number = module_number;
// 根據(jù)name去configuration_hash中查詢,取出來(lái)的結(jié)果放在default_value中
// 注意default_value的值比較原始,一般是數(shù)字、字符串、數(shù)組等,具體取決于php.ini中的寫(xiě)法
if ((zend_get_configuration_directive(p->name, p->name_length, &default_value)) == SUCCESS) {
// 調(diào)用on_modify更新到模塊的全局空間XXX_G中
if (!hashed_ini_entry->on_modify || hashed_ini_entry->on_modify(hashed_ini_entry, Z_STRVAL(default_value), Z_STRLEN(default_value), hashed_ini_entry->mh_arg1, hashed_ini_entry->mh_arg2, hashed_ini_entry->mh_arg3, ZEND_INI_STAGE_STARTUP TSRMLS_CC) == SUCCESS) {
hashed_ini_entry->value = Z_STRVAL(default_value);
hashed_ini_entry->value_length = Z_STRLEN(default_value);
config_directive_success = 1;
}
}
// 如果configuration_hash中沒(méi)有找到,則采用默認(rèn)值
if (!config_directive_success && hashed_ini_entry->on_modify) {
hashed_ini_entry->on_modify(hashed_ini_entry, hashed_ini_entry->value, hashed_ini_entry->value_length, hashed_ini_entry->mh_arg1, hashed_ini_entry->mh_arg2, hashed_ini_entry->mh_arg3, ZEND_INI_STAGE_STARTUP TSRMLS_CC);
}
p++;
}
return SUCCESS;
}
簡(jiǎn)單來(lái)說(shuō),可以把上述代碼的邏輯表述為:
1,將模塊聲明的ini配置項(xiàng)添加到EG(ini_directives)中。注意,ini配置項(xiàng)的值可能在隨后被修改。
2,嘗試去configuration_hash中尋找各個(gè)模塊需要的ini。
如果能夠找到,說(shuō)明用戶叜ini文件中配置了該值,那么采用用戶的配置。
如果沒(méi)有找到,OK,沒(méi)有關(guān)系,因?yàn)槟K在聲明ini的時(shí)候,會(huì)帶上默認(rèn)值。
3,將ini的值同步到XX_G里面。畢竟在php的執(zhí)行過(guò)程中,起作用的還是這些XXX_globals。具體的過(guò)程是調(diào)用每條ini配置對(duì)應(yīng)的on_modify方法完成,on_modify由模塊在聲明ini的時(shí)候進(jìn)行指定。
我們來(lái)具體看下on_modify,它其實(shí)是一個(gè)函數(shù)指針,來(lái)看兩個(gè)具體的Core模塊的配置聲明:
STD_PHP_INI_BOOLEAN("log_errors", "0", PHP_INI_ALL, OnUpdateBool, log_errors, php_core_globals, core_globals)
STD_PHP_INI_ENTRY("log_errors_max_len","1024", PHP_INI_ALL, OnUpdateLong, log_errors_max_len, php_core_globals, core_globals)
對(duì)于log_errors,它的on_modify被設(shè)置為OnUpdateBool,對(duì)于log_errors_max_len,則on_modify被設(shè)置為OnUpdateLong。
進(jìn)一步假設(shè)我們?cè)趐hp.ini中的配置為:
log_errors = On
log_errors_max_len = 1024
具體來(lái)看下OnUpdateBool函數(shù):
ZEND_API ZEND_INI_MH(OnUpdateBool)
{
zend_bool *p;
// base表示core_globals的地址
char *base = (char *) mh_arg2;
// p表示core_globals的地址加上log_errors字段的偏移量
// 得到的即為log_errors字段的地址
p = (zend_bool *) (base+(size_t) mh_arg1);
if (new_value_length == 2 && strcasecmp("on", new_value) == 0) {
*p = (zend_bool) 1;
}
else if (new_value_length == 3 && strcasecmp("yes", new_value) == 0) {
*p = (zend_bool) 1;
}
else if (new_value_length == 4 && strcasecmp("true", new_value) == 0) {
*p = (zend_bool) 1;
}
else {
// configuration_hash中存放的value是字符串"1",而非"On"
// 因此這里用atoi轉(zhuǎn)化成數(shù)字1
*p = (zend_bool) atoi(new_value);
}
return SUCCESS;
}
最令人費(fèi)解的估計(jì)就是mh_arg1和mh_arg2了,其實(shí)對(duì)照前面所述的zend_ini_entry定義,mh_arg1,mh_arg2還是很容易參透的。mh_arg1表示字節(jié)偏移量,mh_arg2表示XXX_globals的地址。因此,(char *)mh_arg2 + mh_arg1的結(jié)果即為XXX_globals中某個(gè)字段的地址。具體到本case中,就是計(jì)算core_globals中l(wèi)og_errors的地址。因此,當(dāng)OnUpdateBool最后執(zhí)行到
*p = (zend_bool) atoi(new_value);
其作用就相當(dāng)于
core_globals.log_errors = (zend_bool) atoi("1");
分析完了OnUpdateBool,我們?cè)賮?lái)看OnUpdateLong便覺(jué)得一目了然:
ZEND_API ZEND_INI_MH(OnUpdateLong)
{
long *p;
char *base = (char *) mh_arg2;
// 獲得log_errors_max_len的地址
p = (long *) (base+(size_t) mh_arg1);
// 將"1024"轉(zhuǎn)化成long型,并賦值給core_globals.log_errors_max_len
*p = zend_atol(new_value, new_value_length);
return SUCCESS;
}
最后需要注意的是,zend_register_ini_entries函數(shù)中,如果configuration_hash中存在配置,則當(dāng)調(diào)用on_modify結(jié)束后,hashed_ini_entry中的value和value_length會(huì)被更新。也就是說(shuō),如果用戶在php.ini中配置過(guò),則EG(ini_directives)存放的就是實(shí)際配置的值。如果用戶沒(méi)配,EG(ini_directives)中存放的是聲明zend_ini_entry時(shí)給出的默認(rèn)值。
zend_register_ini_entries中的default_value變量命名比較糟糕,相當(dāng)容易造成誤解。其實(shí)default_value并非表示默認(rèn)值,而是表示用戶實(shí)際配置的值。
3,總結(jié)
至此,三塊數(shù)據(jù)configuration_hash,EG(ini_directives)以及PG、BG、PCRE_G、JSON_G、XXX_G...已經(jīng)都交代清楚了。
總結(jié)一下:
1,configuration_hash,存放php.ini文件里的配置,不做校驗(yàn),其值為字符串。
2,EG(ini_directives),存放的是各個(gè)模塊中定義的zend_ini_entry,如果用戶在php.ini配置過(guò)(configuration_hash中存在),則值被替換為configuration_hash中的值,類型依然是字符串。
3,XXX_G,該宏用于訪問(wèn)模塊的全局空間,這塊內(nèi)存空間可用來(lái)存放ini配置,并通過(guò)on_modify指定的函數(shù)進(jìn)行更新,其數(shù)據(jù)類型由XXX_G中的字段聲明來(lái)決定。
相關(guān)文章
第四節(jié)--構(gòu)造函數(shù)和析構(gòu)函數(shù)
第四節(jié)--構(gòu)造函數(shù)和析構(gòu)函數(shù)...2006-11-11PHP 選項(xiàng)及相關(guān)信息函數(shù)庫(kù)
PHP 選項(xiàng)及相關(guān)信息函數(shù)庫(kù)...2006-12-12