《PHP實(shí)例:php中的ini配置原理詳解》要點(diǎn):
本文介紹了PHP實(shí)例:php中的ini配置原理詳解,希望對(duì)您有用。如果有疑問(wèn),可以聯(lián)系我們。
使用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實(shí)例
因此本文打算分兩篇,第一篇闡述php.ini配置原理,第二篇講動(dòng)態(tài)修改php配置.PHP實(shí)例
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ì)解釋.PHP實(shí)例
1,解析INI配置文件
PHP實(shí)例
由于php.ini需要在SAPI過(guò)程中一直生效,那么解析ini文件并據(jù)此來(lái)構(gòu)建php配置的工作,必定是發(fā)生SAPI的一開(kāi)始.換句話說(shuō),也就是必定發(fā)生在php的啟動(dòng)過(guò)程中.php需要任意一個(gè)實(shí)際的哀求到達(dá)之前,其內(nèi)部已經(jīng)生成好這些配置.PHP實(shí)例
反映到php的內(nèi)核,即為php_module_startup函數(shù).PHP實(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è)哀求到來(lái)的時(shí)刻進(jìn)行初始化,php_module_startup與php_request_startup是兩個(gè)標(biāo)識(shí)性的動(dòng)作,不過(guò)對(duì)他們進(jìn)行分析并不在本文的探討范圍內(nèi).PHP實(shí)例
舉個(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)在可以接受哀求并作出響應(yīng)了.PHP實(shí)例
在php_module_startup函數(shù)中,與解析ini文件相關(guān)的實(shí)現(xiàn)是:PHP實(shí)例
可以看到,其實(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.PHP實(shí)例
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全貌...真是普大喜奔...PHP實(shí)例
注意四點(diǎn):PHP實(shí)例
第一,php_init_config不會(huì)做除了詞法語(yǔ)法以外的任何校驗(yàn).也就是說(shuō),假如我們?cè)趇ni文件中添加一行 hello=world,只要這是一個(gè)格式正確的配置項(xiàng),那么最終configuration_hash中就會(huì)包含一個(gè)鍵為hello、值為world的元素,configuration_hash最大限度的反映出ini文件.PHP實(shí)例
第二,ini文件允許我們以數(shù)組的形式進(jìn)行配置.例如ini文件中寫(xiě)入以下三行:PHP實(shí)例
那么最終生成的configuration_hash表中,就會(huì)存在一個(gè)key為drift.arr的元素,其value為一個(gè)包含的1,2,3三個(gè)數(shù)字的數(shù)組.這是一種極為罕見(jiàn)的配置方法.PHP實(shí)例
第三,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中去.PHP實(shí)例
這是一個(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文件.PHP實(shí)例
第四,在configuration_hash中,key是字符串,那么值的類型是什么?答案也是字符串(除了上述很特殊的數(shù)組).具體來(lái)說(shuō),比如下面的配置:PHP實(shí)例
?那么最后configuration_hash中實(shí)際存放的鍵值對(duì)為:PHP實(shí)例
key: "log_errors"
val : ""PHP實(shí)例
key: "log_errors_max_len"
val : "1024"
PHP實(shí)例
注意log_errors,其存放的值連"0"都不是,就是一個(gè)實(shí)實(shí)在在地空字符串.另外,log_errors_max_len也并非數(shù)字,而是字符串1024.PHP實(shí)例
分析至此,基本上解析ini文件相關(guān)的內(nèi)容都說(shuō)清楚了.簡(jiǎn)單總結(jié)一下:PHP實(shí)例
1,解析ini發(fā)生在php_module_startup階段PHP實(shí)例
2,解析結(jié)果存放在configuration_hash里.PHP實(shí)例
2,配置作用到模塊
PHP實(shí)例
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模塊提供的.PHP實(shí)例
為什么需要談到這些,是因?yàn)樵趐hp.ini里除了針對(duì)php自身,也就是針對(duì)Core模塊的一些配置(例如safe_mode,display_errors,max_execution_time等),還有相當(dāng)多的配置是針對(duì)其他不同模塊的.PHP實(shí)例
例如,date模塊,它提供了常見(jiàn)的date, time,strtotime等函數(shù).在php.ini中,它的相關(guān)配置形如:PHP實(shí)例
除了這些模塊擁有獨(dú)立的配置,zend引擎也是可配的,只不過(guò)zend引擎的可配項(xiàng)非常少,只有error_reporting,zend.enable_gc和detect_unicode三項(xiàng).PHP實(shí)例
在上一小節(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ò)程中.PHP實(shí)例
有擴(kuò)展開(kāi)發(fā)經(jīng)驗(yàn)的同學(xué)會(huì)直接指出,模塊A的啟動(dòng)不就是在PHP_MINIT_FUNCTION(A)中么?PHP實(shí)例
是的,如果模塊A需要配置,那么在PHP_MINIT_FUNCTION中,可以調(diào)用REGISTER_INI_ENTRIES()來(lái)完成.REGISTER_INI_ENTRIES會(huì)根據(jù)當(dāng)前模塊所需要的配置項(xiàng)名稱,去configuration_hash查找用戶設(shè)置的配置值,并更新到模塊自己的全局空間中.PHP實(shí)例
2.1,模塊的全局空間
PHP實(shí)例
要理解如何將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ò)程中,只需要直接拜訪這塊全局空間,就可以拿到用戶針對(duì)該模塊進(jìn)行的設(shè)置.當(dāng)然,它也經(jīng)常被用來(lái)記錄模塊在執(zhí)行過(guò)程中的中間數(shù)據(jù).PHP實(shí)例
我們以bcmath模塊來(lái)舉例說(shuō)明,bcmath是一個(gè)提供數(shù)學(xué)計(jì)算方面接口的php模塊,首先我們來(lái)看看它有哪些ini配置:PHP實(shí)例
bcmath只有一個(gè)配置項(xiàng),我們可以在php.ini中用bcmath.scale來(lái)配置bcmath模塊.PHP實(shí)例
接下來(lái)繼續(xù)看看bcmatch模塊的全局空間定義.在php_bcmath.h中有如下聲明:PHP實(shí)例
?宏展開(kāi)之后,即為:PHP實(shí)例
其實(shí),zend_bcmath_globals類型就是bcmath模塊中的全局空間類型.這里僅僅聲明了zend_bcmath_globals結(jié)構(gòu)體,在bcmath.c中還有具體的實(shí)例化定義:PHP實(shí)例
// 展開(kāi)后即為zend_bcmath_globals bcmath_globals;
ZEND_DECLARE_MODULE_GLOBALS(bcmath)
可以看出,用ZEND_DECLARE_MODULE_GLOBALS完成了對(duì)變量bcmath_globals的定義.PHP實(shí)例
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中去.PHP實(shí)例
把configuration_hash中的值,更新到各個(gè)模塊自己定義的xxx_globals變量中,就是所謂的將ini配置作用到模塊.一旦模塊啟動(dòng)完成,那么這些配置也都作用到位.所以在隨后的執(zhí)行階段,php模塊無(wú)需再次拜訪configuration_hash,模塊僅需要拜訪自己的XXX_globals,就可以拿到用戶設(shè)定的配置.PHP實(shí)例
bcmath_globals,除了有一個(gè)字段為ini配置項(xiàng),其他還有三個(gè)字段為何意?這就是模塊全局空間的第二個(gè)作用,它除了用于ini配置,還可以存儲(chǔ)模塊執(zhí)行過(guò)程中的一些數(shù)據(jù).PHP實(shí)例
PHP實(shí)例
再例如json模塊,也是php中一個(gè)很常用的模塊:PHP實(shí)例
可以看到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ò)誤原因.PHP實(shí)例
為了能夠很便捷的拜訪模塊全局空間變量,php約定俗成的提出了一些宏.比如我們想拜訪json_globals中的error_code,當(dāng)然可以直接寫(xiě)做json_globals.error_code(多線程環(huán)境下不行),不過(guò)更通用的寫(xiě)法是定義JSON_G宏:PHP實(shí)例
我們使用JSON_G(error_code)來(lái)拜訪json_globals.error_code.本文剛開(kāi)始的時(shí)候,曾提到PG、BG、JSON_G、PCRE_G,XXX_G等等,這些宏在php源代碼中也是很常見(jiàn)的.現(xiàn)在我們可以很輕松的理解它們,PG宏可以拜訪Core模塊的全局變量,BG拜訪Standard模塊的全局變量,PCRE_G則拜訪PCRE模塊的全局變量.PHP實(shí)例
2.2,如何確定一個(gè)模塊需要哪些配置呢?
PHP實(shí)例
模塊需要什么樣的INI配置,都是在各個(gè)模塊中自己定義的.舉例來(lái)說(shuō),對(duì)于Core模塊,有如下的配置項(xiàng)定義:PHP實(shí)例
可以在php-src\main\main.c文件大概450+行找到上述代碼.其中涉及的宏比較多,有ZEND_INI_BEGIN 、ZEND_INI_END、PHP_INI_ENTRY_EX、STD_PHP_INI_BOOLEAN等等,本文不一一贅述,感興趣的讀者可自行分析.PHP實(shí)例
上述代碼進(jìn)行宏展開(kāi)后得到:PHP實(shí)例
我們看到,配置項(xiàng)的定義,其本質(zhì)上就是定義了一個(gè)zend_ini_entry類型的數(shù)組.zend_ini_entry結(jié)構(gòu)體的字段具體含義為:PHP實(shí)例
??? char *value;????????????????????? // 配置項(xiàng)的值
??? uint value_length;PHP實(shí)例
??? char *orig_value;???????????????? // 配置項(xiàng)的原始值
??? uint orig_value_length;
??? int orig_modifiable;????????????? // 配置項(xiàng)的原始modifiable
??? int modified;???????????????????? // 是否發(fā)生過(guò)修改,如果有修改,則orig_value會(huì)保存修改前的值PHP實(shí)例
??? void (*displayer)(zend_ini_entry *ini_entry, int type);
};
PHP實(shí)例
2.3,將配置作用到模塊――REGISTER_INI_ENTRIES
PHP實(shí)例
經(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).PHP實(shí)例
REGISTER_INI_ENTRIES也是一個(gè)宏,展開(kāi)之后實(shí)則為zend_register_ini_entries方法.具體來(lái)看下zend_register_ini_entries的實(shí)現(xiàn):PHP實(shí)例
??????? // 如果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;
}
PHP實(shí)例
簡(jiǎn)單來(lái)說(shuō),可以把上述代碼的邏輯表述為:PHP實(shí)例
1,將模塊聲明的ini配置項(xiàng)添加到EG(ini_directives)中.注意,ini配置項(xiàng)的值可能在隨后被修改.PHP實(shí)例
2,嘗試去configuration_hash中尋找各個(gè)模塊需要的ini.PHP實(shí)例
如果能夠找到,說(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)行指定.PHP實(shí)例
我們來(lái)具體看下on_modify,它其實(shí)是一個(gè)函數(shù)指針,來(lái)看兩個(gè)具體的Core模塊的配置聲明:PHP實(shí)例
對(duì)于log_errors,它的on_modify被設(shè)置為OnUpdateBool,對(duì)于log_errors_max_len,則on_modify被設(shè)置為OnUpdateLong.PHP實(shí)例
進(jìn)一步假設(shè)我們?cè)趐hp.ini中的配置為:PHP實(shí)例
具體來(lái)看下OnUpdateBool函數(shù):PHP實(shí)例
??? // p表示core_globals的地址加上log_errors字段的偏移量
??? // 得到的即為log_errors字段的地址
??? p = (zend_bool *) (base+(size_t) mh_arg1);?PHP實(shí)例
??? 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;
}
PHP實(shí)例
最令人費(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í)行到PHP實(shí)例
其作用就相當(dāng)于PHP實(shí)例
分析完了OnUpdateBool,我們?cè)賮?lái)看OnUpdateLong便覺(jué)得一目了然:PHP實(shí)例
??? // 獲得log_errors_max_len的地址
??? p = (long *) (base+(size_t) mh_arg1);PHP實(shí)例
??? // 將"1024"轉(zhuǎn)化成long型,并賦值給core_globals.log_errors_max_len
??? *p = zend_atol(new_value, new_value_length);
??? return SUCCESS;
}
PHP實(shí)例
最后需要注意的是,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)值.PHP實(shí)例
zend_register_ini_entries中的default_value變量命名比較糟糕,相當(dāng)容易造成誤解.其實(shí)default_value并非表示默認(rèn)值,而是表示用戶實(shí)際配置的值.PHP實(shí)例
3,總結(jié)
PHP實(shí)例
至此,三塊數(shù)據(jù)configuration_hash,EG(ini_directives)以及PG、BG、PCRE_G、JSON_G、XXX_G...已經(jīng)都交代清楚了.PHP實(shí)例
總結(jié)一下:PHP實(shí)例
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,該宏用于拜訪模塊的全局空間,這塊內(nèi)存空間可用來(lái)存放ini配置,并通過(guò)on_modify指定的函數(shù)進(jìn)行更新,其數(shù)據(jù)類型由XXX_G中的字段聲明來(lái)決定.PHP實(shí)例
《PHP實(shí)例:php中的ini配置原理詳解》是否對(duì)您有啟發(fā),歡迎查看更多與《PHP實(shí)例:php中的ini配置原理詳解》相關(guān)教程,學(xué)精學(xué)透。維易PHP學(xué)院為您提供精彩教程。
轉(zhuǎn)載請(qǐng)注明本頁(yè)網(wǎng)址:
http://www.fzlkiss.com/jiaocheng/14559.html