《PHP實例:php中的ini配置原理詳解》要點:
本文介紹了PHP實例:php中的ini配置原理詳解,希望對您有用。如果有疑問,可以聯系我們。
使用php的同學都知道php.ini配置的生效會貫穿整個SAPI的生命周期.在一段php腳本的執行過程中,如果手動修改ini配置,是不會啟作用的.此時如果無法重啟apache或者nginx等,那么就只能顯式的在php代碼中調用ini_set接口.ini_set是php向我們提供的一個動態修改配置的函數,需要注意的是,利用ini_set所設置的配置與ini文件中設置的配置,其生效的時間范圍并不相同.在php腳本執行結束之后,ini_set的設置便會隨即失效.PHP實例
因此本文打算分兩篇,第一篇闡述php.ini配置原理,第二篇講動態修改php配置.PHP實例
php.ini的配置大致會涉及到三塊數據,configuration_hash,EG(ini_directives)以及PG、BG、PCRE_G、JSON_G、XXX_G等.如果不清楚這三種數據的含義也沒有關系,下文會詳細解釋.PHP實例
1,解析INI配置文件
PHP實例
由于php.ini需要在SAPI過程中一直生效,那么解析ini文件并據此來構建php配置的工作,必定是發生SAPI的一開始.換句話說,也就是必定發生在php的啟動過程中.php需要任意一個實際的哀求到達之前,其內部已經生成好這些配置.PHP實例
反映到php的內核,即為php_module_startup函數.PHP實例
php_module_startup主要負責對php進行啟動,通常它會在SAPI開始的時候被調用.btw,還有一個常見的函數是php_request_startup,它負責將在每個哀求到來的時刻進行初始化,php_module_startup與php_request_startup是兩個標識性的動作,不過對他們進行分析并不在本文的探討范圍內.PHP實例
舉個例子,當php掛接在apache下面做一個module,那么apache啟動的時候,便會激活所有這些module,其中包括php module.在激活php module時,便會調用到php_module_startup.php_module_startup函數完成了茫茫多的工作,一旦php_module_startup調用結束就意味著,OK,php已經啟動,現在可以接受哀求并作出響應了.PHP實例
在php_module_startup函數中,與解析ini文件相關的實現是:PHP實例
可以看到,其實就是調用了php_init_config函數,去完成對ini文件的parse.parse工作主要進行lex&grammar分析,并將ini文件中的key、value鍵值對提取出來并保存.php.ini的格式很簡單,等號左側為key,右側為value.每當一對kv被提取出來之后,php將它們存儲到哪兒呢?答案就是之前提到的configuration_hash.PHP實例
static HashTable configuration_hash;
configuration_hash聲明在php_ini.c中,它是一個HashTable類型的數據結構.顧名思義,其實就是張hash表.題外話,在php5.3之前的版本是沒法獲取configuration_hash的,因為它是php_ini.c文件的一個static的變量.后來php5.3添加了php_ini_get_configuration_hash接口,該接口直接返回&configuration_hash,使 得php各個擴展可以方便的一窺configuration_hash全貌...真是普大喜奔...PHP實例
注意四點:PHP實例
第一,php_init_config不會做除了詞法語法以外的任何校驗.也就是說,假如我們在ini文件中添加一行 hello=world,只要這是一個格式正確的配置項,那么最終configuration_hash中就會包含一個鍵為hello、值為world的元素,configuration_hash最大限度的反映出ini文件.PHP實例
第二,ini文件允許我們以數組的形式進行配置.例如ini文件中寫入以下三行:PHP實例
那么最終生成的configuration_hash表中,就會存在一個key為drift.arr的元素,其value為一個包含的1,2,3三個數字的數組.這是一種極為罕見的配置方法.PHP實例
第三,php還允許我們除了默認的php.ini文件(準確說是php-%s.ini)之外,另外構建一些ini文件.這些ini文件會被放入一個額外的目錄.該目錄由環境變量PHP_INI_SCAN_DIR來指定,當php_init_config解析完了php.ini之后,會再次掃描此目錄,然后找出目錄中所有.ini文件來分析.這些額外的ini文件中產生的kv鍵值對,也會被加入到configuration_hash中去.PHP實例
這是一個偶爾有用的特性,假設我們自己開發php的擴展,卻又不想將配置混入php.ini,便可以另外寫一份ini,并通過PHP_INI_SCAN_DIR告訴php該去哪兒找到它.當然,其缺點也顯而易見,其需要設置額外的環境變量來支持.更好的解決辦法是,開發者在擴展中自己調用php_parse_user_ini_file或zend_parse_ini_file去解析對應的ini文件.PHP實例
第四,在configuration_hash中,key是字符串,那么值的類型是什么?答案也是字符串(除了上述很特殊的數組).具體來說,比如下面的配置:PHP實例
?那么最后configuration_hash中實際存放的鍵值對為:PHP實例
key: "log_errors"
val : ""PHP實例
key: "log_errors_max_len"
val : "1024"
PHP實例
注意log_errors,其存放的值連"0"都不是,就是一個實實在在地空字符串.另外,log_errors_max_len也并非數字,而是字符串1024.PHP實例
分析至此,基本上解析ini文件相關的內容都說清楚了.簡單總結一下:PHP實例
1,解析ini發生在php_module_startup階段PHP實例
2,解析結果存放在configuration_hash里.PHP實例
2,配置作用到模塊
PHP實例
php的大致結構可以看成是最下層有一個zend引擎,它負責與OS進行交互、編譯php代碼、提供內存托管等等,在zend引擎的上層,排列著很多很多的模塊.其中最核心的就一個Core模塊,其他還有比如Standard,PCRE,Date,Session等等...這些模塊還有另一個名字叫php擴展.我們可以簡單理解為,每個模塊都會提供一組功能接口給開發者來調用,舉例來說,常用的諸如explode,trim,array等內置函數,便是由Standard模塊提供的.PHP實例
為什么需要談到這些,是因為在php.ini里除了針對php自身,也就是針對Core模塊的一些配置(例如safe_mode,display_errors,max_execution_time等),還有相當多的配置是針對其他不同模塊的.PHP實例
例如,date模塊,它提供了常見的date, time,strtotime等函數.在php.ini中,它的相關配置形如:PHP實例
除了這些模塊擁有獨立的配置,zend引擎也是可配的,只不過zend引擎的可配項非常少,只有error_reporting,zend.enable_gc和detect_unicode三項.PHP實例
在上一小節中我們已經談到,php_module_startup會調用php_init_config,其目的是解析ini文件并生成configuration_hash.那么接下來在php_module_startup中還會做什么事情呢?很顯然,就是會將configuration_hash中的配置作用于Zend,Core,Standard,SPL等不同模塊.當然這并非一個一蹴而就的過程,因為php通常會包含有很多模塊,php啟動的過程中這些模塊也會依次進行啟動.那么,對模塊A進行配置的過程,便是發生在模塊A的啟動過程中.PHP實例
有擴展開發經驗的同學會直接指出,模塊A的啟動不就是在PHP_MINIT_FUNCTION(A)中么?PHP實例
是的,如果模塊A需要配置,那么在PHP_MINIT_FUNCTION中,可以調用REGISTER_INI_ENTRIES()來完成.REGISTER_INI_ENTRIES會根據當前模塊所需要的配置項名稱,去configuration_hash查找用戶設置的配置值,并更新到模塊自己的全局空間中.PHP實例
2.1,模塊的全局空間
PHP實例
要理解如何將ini配置從configuration_hash作用到各個模塊之前,有必要先了解一下php模塊的全局空間.對于不同的php模塊,均可以開辟一塊屬于自己的存儲空間,并且這塊空間對于該模塊來說,是全局可見的.一般而言,它會被用來存放該模塊所需的ini配置.也就是說,configuration_hash中的配置項,最終會被存放到該全局空間中.在模塊的執行過程中,只需要直接拜訪這塊全局空間,就可以拿到用戶針對該模塊進行的設置.當然,它也經常被用來記錄模塊在執行過程中的中間數據.PHP實例
我們以bcmath模塊來舉例說明,bcmath是一個提供數學計算方面接口的php模塊,首先我們來看看它有哪些ini配置:PHP實例
bcmath只有一個配置項,我們可以在php.ini中用bcmath.scale來配置bcmath模塊.PHP實例
接下來繼續看看bcmatch模塊的全局空間定義.在php_bcmath.h中有如下聲明:PHP實例
?宏展開之后,即為:PHP實例
其實,zend_bcmath_globals類型就是bcmath模塊中的全局空間類型.這里僅僅聲明了zend_bcmath_globals結構體,在bcmath.c中還有具體的實例化定義:PHP實例
// 展開后即為zend_bcmath_globals bcmath_globals;
ZEND_DECLARE_MODULE_GLOBALS(bcmath)
可以看出,用ZEND_DECLARE_MODULE_GLOBALS完成了對變量bcmath_globals的定義.PHP實例
bcmath_globals是一塊真正的全局空間,它包含有四個字段.其最后一個字段bc_precision,對應于ini配置中的bcmath.scale.我們在php.ini中設置了bcmath.scale的值,隨后在啟動bcmath模塊的時候,bcmath.scale的值被更新到bcmath_globals.bc_precision中去.PHP實例
把configuration_hash中的值,更新到各個模塊自己定義的xxx_globals變量中,就是所謂的將ini配置作用到模塊.一旦模塊啟動完成,那么這些配置也都作用到位.所以在隨后的執行階段,php模塊無需再次拜訪configuration_hash,模塊僅需要拜訪自己的XXX_globals,就可以拿到用戶設定的配置.PHP實例
bcmath_globals,除了有一個字段為ini配置項,其他還有三個字段為何意?這就是模塊全局空間的第二個作用,它除了用于ini配置,還可以存儲模塊執行過程中的一些數據.PHP實例
PHP實例
再例如json模塊,也是php中一個很常用的模塊:PHP實例
可以看到json模塊并不需要ini配置,它的全局空間只有一個字段error_code.error_code記錄了上一次執行json_decode或者json_encode中發生的錯誤.json_last_error函數便是返回這個error_code,來幫助用戶定位錯誤原因.PHP實例
為了能夠很便捷的拜訪模塊全局空間變量,php約定俗成的提出了一些宏.比如我們想拜訪json_globals中的error_code,當然可以直接寫做json_globals.error_code(多線程環境下不行),不過更通用的寫法是定義JSON_G宏:PHP實例
我們使用JSON_G(error_code)來拜訪json_globals.error_code.本文剛開始的時候,曾提到PG、BG、JSON_G、PCRE_G,XXX_G等等,這些宏在php源代碼中也是很常見的.現在我們可以很輕松的理解它們,PG宏可以拜訪Core模塊的全局變量,BG拜訪Standard模塊的全局變量,PCRE_G則拜訪PCRE模塊的全局變量.PHP實例
2.2,如何確定一個模塊需要哪些配置呢?
PHP實例
模塊需要什么樣的INI配置,都是在各個模塊中自己定義的.舉例來說,對于Core模塊,有如下的配置項定義:PHP實例
可以在php-src\main\main.c文件大概450+行找到上述代碼.其中涉及的宏比較多,有ZEND_INI_BEGIN 、ZEND_INI_END、PHP_INI_ENTRY_EX、STD_PHP_INI_BOOLEAN等等,本文不一一贅述,感興趣的讀者可自行分析.PHP實例
上述代碼進行宏展開后得到:PHP實例
我們看到,配置項的定義,其本質上就是定義了一個zend_ini_entry類型的數組.zend_ini_entry結構體的字段具體含義為:PHP實例
??? char *value;????????????????????? // 配置項的值
??? uint value_length;PHP實例
??? char *orig_value;???????????????? // 配置項的原始值
??? uint orig_value_length;
??? int orig_modifiable;????????????? // 配置項的原始modifiable
??? int modified;???????????????????? // 是否發生過修改,如果有修改,則orig_value會保存修改前的值PHP實例
??? void (*displayer)(zend_ini_entry *ini_entry, int type);
};
PHP實例
2.3,將配置作用到模塊――REGISTER_INI_ENTRIES
PHP實例
經常能夠在不同擴展的PHP_MINIT_FUNCTION里看到REGISTER_INI_ENTRIES.REGISTER_INI_ENTRIES主要負責完成兩件事情,第一,對模塊的全局空間XXX_G進行填充,同步configuration_hash中的值到XXX_G中去.其次,它還生成了EG(ini_directives).PHP實例
REGISTER_INI_ENTRIES也是一個宏,展開之后實則為zend_register_ini_entries方法.具體來看下zend_register_ini_entries的實現:PHP實例
??????? // 如果configuration_hash中沒有找到,則采用默認值
??????? 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實例
簡單來說,可以把上述代碼的邏輯表述為:PHP實例
1,將模塊聲明的ini配置項添加到EG(ini_directives)中.注意,ini配置項的值可能在隨后被修改.PHP實例
2,嘗試去configuration_hash中尋找各個模塊需要的ini.PHP實例
如果能夠找到,說明用戶ini文件中配置了該值,那么采用用戶的配置.
如果沒有找到,OK,沒有關系,因為模塊在聲明ini的時候,會帶上默認值.
3,將ini的值同步到XX_G里面.畢竟在php的執行過程中,起作用的還是這些XXX_globals.具體的過程是調用每條ini配置對應的on_modify方法完成,on_modify由模塊在聲明ini的時候進行指定.PHP實例
我們來具體看下on_modify,它其實是一個函數指針,來看兩個具體的Core模塊的配置聲明:PHP實例
對于log_errors,它的on_modify被設置為OnUpdateBool,對于log_errors_max_len,則on_modify被設置為OnUpdateLong.PHP實例
進一步假設我們在php.ini中的配置為:PHP實例
具體來看下OnUpdateBool函數:PHP實例
??? // p表示core_globals的地址加上log_errors字段的偏移量
??? // 得到的即為log_errors字段的地址
??? p = (zend_bool *) (base+(size_t) mh_arg1);?PHP實例
??? 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轉化成數字1
??????? *p = (zend_bool) atoi(new_value);
??? }
??? return SUCCESS;
}
PHP實例
最令人費解的估計就是mh_arg1和mh_arg2了,其實對照前面所述的zend_ini_entry定義,mh_arg1,mh_arg2還是很容易參透的.mh_arg1表示字節偏移量,mh_arg2表示XXX_globals的地址.因此,(char *)mh_arg2 + mh_arg1的結果即為XXX_globals中某個字段的地址.具體到本case中,就是計算core_globals中log_errors的地址.因此,當OnUpdateBool最后執行到PHP實例
其作用就相當于PHP實例
分析完了OnUpdateBool,我們再來看OnUpdateLong便覺得一目了然:PHP實例
??? // 獲得log_errors_max_len的地址
??? p = (long *) (base+(size_t) mh_arg1);PHP實例
??? // 將"1024"轉化成long型,并賦值給core_globals.log_errors_max_len
??? *p = zend_atol(new_value, new_value_length);
??? return SUCCESS;
}
PHP實例
最后需要注意的是,zend_register_ini_entries函數中,如果configuration_hash中存在配置,則當調用on_modify結束后,hashed_ini_entry中的value和value_length會被更新.也就是說,如果用戶在php.ini中配置過,則EG(ini_directives)存放的就是實際配置的值.如果用戶沒配,EG(ini_directives)中存放的是聲明zend_ini_entry時給出的默認值.PHP實例
zend_register_ini_entries中的default_value變量命名比較糟糕,相當容易造成誤解.其實default_value并非表示默認值,而是表示用戶實際配置的值.PHP實例
3,總結
PHP實例
至此,三塊數據configuration_hash,EG(ini_directives)以及PG、BG、PCRE_G、JSON_G、XXX_G...已經都交代清楚了.PHP實例
總結一下:PHP實例
1,configuration_hash,存放php.ini文件里的配置,不做校驗,其值為字符串.
2,EG(ini_directives),存放的是各個模塊中定義的zend_ini_entry,如果用戶在php.ini配置過(configuration_hash中存在),則值被替換為configuration_hash中的值,類型依然是字符串.
3,XXX_G,該宏用于拜訪模塊的全局空間,這塊內存空間可用來存放ini配置,并通過on_modify指定的函數進行更新,其數據類型由XXX_G中的字段聲明來決定.PHP實例
《PHP實例:php中的ini配置原理詳解》是否對您有啟發,歡迎查看更多與《PHP實例:php中的ini配置原理詳解》相關教程,學精學透。維易PHP學院為您提供精彩教程。