# 13.1 聲明和訪問INI設置
INI條目被定義在一個完整的獨立的的塊,位于上文中所說的MINIT方法的同一個源文件,并且用下面的一對宏來定義,并在這對宏之間放入一個或者多個條目`PHP_INI_BEGIN()`和`PHP_INI_END()`
這些宏方法和上一章所提到的`ZEND_BEGIN_MODULE_GLOBALS()`和`ZEND_END_MODULE_GLOBALS()`有著相同的用法。這些結構是用靜態數據的實例來聲明,而不僅僅是提供一個結構的定義
````c
static zend_ini_entry ini_entries[] = {
{0,0,NULL,0,NULL,NULL,NULL,NULL,NULL,0,NULL,0,0,NULL} };
````
正如你所看到的,上面定義了`zend_ini_entry`的一個向量值并以一個空記錄來終止。你或許已經多次在`function_entry`結構的定義之中看到過這種以填充靜態向量的方法
####簡單INI設置
現在你可以使用 INI 結構來聲明條目,這個機制是用來注冊和銷毀一些在機器上的設置,你可以聲明一些對你的擴展有用的有實際意義的設置了。
假設你的擴展的方法就像你在第五章看到的那個例子(`Your First Extension`)一樣,只是輸出一個簡單的問候,你可能想讓你要輸出的問候語句是可定制的:
````c
PHP_FUNCTION(sample4_hello_world)
{
php_printf("Hello World!\n");
}
````
最簡單的方法就是定義一個 INI 的設置,讓它的默認值為`Hello World!`,像下面這樣:
````c
#include "php_ini.h"
PHP_INI_BEGIN()
PHP_INI_ENTRY("sample4.greeting", "Hello World", PHP_INI_ALL, NULL)
PHP_INI_END()
````
正如你猜測的一樣,PHP_INI_ENTRY 這個宏里面設置的前面的兩個參數,分別代表著INI設置的名稱和它的默認值。第三個參數決定設置是否允許被修改,以及它能被修改的作用域。最后一個參數是一個回調函數,當INI的值被修改時候觸發此回調函數。你將會在某些修改事件的地方詳細的了解這個參數。
PHP總共有4個指令配置作用域:(PHP中的每個指令都有自己的作用域,指令只能在其作用域中修改,不是任何地方都能修改配置指令的)
Parameter | Meaning
------------- | -------------
PHP_INI_PERDIR | 指令可以在php.ini、httpd.conf或.htaccess文件中修改
PHP_INI_SYSTEM | 指令可以在php.ini 和 httpd.conf 文件中修改
PHP_INI_USER | 指令可以在用戶腳本中修改
PHP_INI_ALL | 指令可以在任何地方修改
現在你已經聲明了你的INI的設置,你現在準備將它用在你的問候函數之中:
````c
PHP_FUNCTION(sample4_hello_world)
{
const char *greeting = INI_STR("sample4.greeting");
php_printf("%s\n", greeting);
}
````
有一點很重要:`char*`類型的值被認為是屬于`ZEND`引擎的,是不能修改的。正因為如此,你將本地變量設置進INI時候,它將在你的方法中被聲明為`const`。并不是所有的INI值都是基于字符串的;也有其他的一些用于整數、浮點數、或布爾值的宏,例如:
````c
long lval = INI_INT("sample4.intval");
double dval = INI_FLT("sample4.fltval");
zend_bool bval = INI_BOOL("sample4.boolval");
````
通常你想知道你當前的`INI`設置的值;恭喜你,ZEND內核剛好就存在一組這樣的宏為你提供查詢每種類型的INI的默認值。
````c
const char *strval = INI_ORIG_STR("sample4.stringval");
long lval = INI_ORIG_INT("sample4.intval");
double dval = INI_ORIG_FLT("sample4.fltval");
zend_bool bval = INI_ORIG_BOOL("sample4.boolval");
````
#####NOTE
在這個例子中,`sample4.greeting`只是INI設置的一個前綴,用來幫助保證它的設置不會和其他擴展的設置相沖突。這種加前綴的方法不僅僅是用在閉源的擴展上,它更被廣泛的用在任何商業或者開源的公開發布的擴展上面。
####訪問級別
每一個給出的INI值總是有默認值的。在許多情況下,INI都有一個比較合理的默認值;然而,在某些比較特殊的環境或者某個腳本要做一些比較特別的事情的情況下,這些INI值通常會被修改。這些設置可以在任何的三個不同的點被修改,看下表:
Access Level | Meaning
------------- | -------------
SYSTEM | 設置被放在php.ini中,或者在Apache的http.conf配置文件中的<Directory>和<VirtualHost>,它在apache啟動時候生效,被認為是設置的全局變量
PERDIR | 一些設置被放在Apache的http.conf的<Directory>或者<VirtualHost>塊中,或者.htaccess文件之中。原文:Any setting found in a <Directory> or <VirtualHost> block within Apache's httpd.conf, or settings located in .htaccess files as well as certain other locations not exclusive to Apache are processed just prior to a given request if that request is within the appropriate directory or virtual host.
USER | 一旦腳本開始執行,唯一的改變INI設置的方法就是利用用戶方法:ini_set()
某些設置,例如safe_mode,如果他們能在任何地方任意修改的話,那它的存在就沒意義了。例如,一個惡意腳本的作者可以簡單的禁用safe_mode,然后任意讀取和修改其他的被禁止的文件。
同樣,一些非安全相關的設置,如:register_globals或magic_quotes_gpc 不能在一個腳本中有效的被改變,因為他們所承擔的任務已經過了。
Similarly, some non-security related settings such as register_globals or magic_quotes_gpc cannot be effectively changed within a script because the point at which they bear relevance has already passed.
這些設置是通過`PHP_INI_ENTRY()`的第三個參數來設置的。在你的設置聲明之中,你已經使用了 `PHP_INI_ALL`, 他們是按位或者`PHP_INI_SYSTEM | PHP_INI_PERDIR | PHP_INI_USER`的組合來定義的。
如register_globals和magic_quotes_gpc的設置,反過來,使用`PHP_INI_SYSTEM | PHP_INI_PERDIR` 來聲明。對于這些設置,在任何調用`ini_set()`的地方排除`PHP_INI_USER`的話將以失敗告終(The exclusion of PHP_INI_USER results in any call to ini_set() for these settings ending in failure)。
現在你可能會猜測,safe_mode和open_basedir之類的設置只能用`PHP_INI_SYSTEM`來聲明。這種設置可以確保只有系統管理員才能修改這些值,因為只有他們有權限修改php.ini或者http.conf中的值。
####修改事件
無論INI設置在什么時候被修改,無論是通過`ini_set()`方法來修改還是在一個`perdir`指令執行期間來修改,zend引擎都會通過一個`OnModify`的回調來檢查它。做修改的人可能會通過使用`ZEND_INI_MH`宏來定義,然后通過`OnModify`方法的參數來附加到INI設置里面:
````c
ZEND_INI_MH(php_sample4_modify_greeting)
{
if (new_value_length == 0) {
return FAILURE;
}
return SUCCESS;
}
PHP_INI_BEGIN()
PHP_INI_ENTRY("sample4.greeting", "Hello World",
PHP_INI_ALL, php_sample4_modify_greeting)
PHP_INI_END()
````
當`new_value_length`的長度為0的時候返回FAILURE,這樣修改者就可以禁止將祝福語句設置為一個空字符串。像下面這樣使用`ZEND_INI_MH()`可以生成整個原型:
````c
int php_sample4_modify_greeting(zend_ini_entry *entry,
char *new_value, uint new_value_length,
void *mh_arg1, void *mh_arg2, void *mh_arg3,
int stage TSRMLS_DC);
````
#####Table 13.2. INI Setting Modifier Callback Parameters
Parameter | Meaning
------------- | -------------
enTRy | 指向zend引擎實際存儲的INI設置,這種結構提供一些信息,包括現在的值、原始值、擁有模塊、還有其他的一些在下面的 Listing 13.1 中的詳細信息
new_value | 關于設置的值。如果處理方法返回SUCCESS,這個值將被填充進 enTRy->value ,如果 entry->orig_value 至今沒有設置的話,當前的值將被旋轉到這個位置,并且也會設置 enTRy->modified 這個標志。字符串的長度將被填充到 new_value_length。
mh_arg1,2,3 | 這組指針(三個一組)提供了訪問數據指針最初給出的INI設置的聲明。在實踐中,這些值都是通過內部引擎進程來調用的,所以你不需要擔心它們
stage | ZEND_INI_STAGE_s 這種形式里面有五個值,這五個由s代表的值為STARTUP, SHUTDOWN, ACTIVATE, DEACTIVATE, 或者 RUNTIME,這些常量分別對應 MINIT, MSHUTDOWN, RINIT, RSHUTDOWN 還有 處于活躍狀態正在執行的腳本。
#####Listing 13.1. Core structure: zend_ini_entry
````c
struct _zend_ini_entry {
int module_number;
int modifiable;
char *name;
uint name_length;
ZEND_INI_MH((*on_modify));
void *mh_arg1;
void *mh_arg2;
void *mh_arg3;
char *value;
uint value_length;
char *orig_value;
uint orig_value_length;
int modified;
void ZEND_INI_DISP(*displayer);
};
````
####顯示INI設置
在上一章,你已經見過`MINFO`方法,還有一些其他關于擴展的基礎知識。因為輸出INI的信息對于一個擴展來說是很正常的,ZEND引擎有一個統一的宏來輸出這些內容,它可以被放置在`PHP_MINFO_FUNCTION()`塊中:
````c
PHP_MINFO_FUNCTION(sample4)
{
DISPLAY_INI_ENTRIES();
}
````
這個宏需要INI設置的值,而這個值早已經在前面定義在了`PHP_INI_BEGIN`和`PHP_INI_END`宏之間。INI設置被迭代的顯示在一個三列的表單中,這三列分別是INI設置的名字,它的原始(全局)設置,還有通過`PERDIR`指令或者`ini_set()`修改過的當前的設置。
默認情況下,所有的設置的條目都是根據原有的字符串來簡單的輸出的。(By default, all entries are simply output according to their string representation as-is.)一些類似布爾值和語法高亮顯示的顏色的值,在顯示的時候會經過特殊的格式化處理。這種特殊格式化的方法是通過每個INI設置自己的顯示處理程序來處理的,這個處理程序是通過動態指向一個回調函數來實現的,跟我們前面看到的 OnModify 類似。
顯示處理程序指定使用一個擴展的`PHP_INI_ENTRY()`宏的版本,`PHP_INI_ENTRY()`接受一個額外的參數。如果將它設置為NULL,默認的顯示處理程序將按照字符串的值原樣使用。
````c
PHP_INI_ENTRY_EX("sample4.greeting", "Hello World", PHP_INI_ALL,
php_sample4_modify_greeting, php_sample4_display_greeting)
````
明顯的,這個回調需要在INI設置使用之前提前的定義好。與 OnModify 的回調類似,這個用一個包裝宏來完成,并且只用少量的代碼就能實現:
````c
#include "SAPI.h" /* needed for sapi_module */
PHP_INI_DISP(php_sample4_display_greeting)
{
const char *value = ini_entry->value;
/* Select the current or original value as appropriate */
if (type == ZEND_INI_DISPLAY_ORIG &&
ini_entry->modified) {
value = ini_entry->orig_value;
}
/* Make the greeting bold (when HTML output is enabled) */
if (sapi_module.phpinfo_as_text) {
php_printf("%s", value);
} else {
php_printf("<b>%s</b>", value);
}
}
````
####綁定到擴展的全局設置
所有的INI條目都在Zend Engine中被給予了存儲空間,用來在腳本之中追蹤它的改變,并且在請求之外維持全局設置。在這個存儲空間之中,所有的INI設置都是以字符串形式保存的。你應該會想到,這些值可以使用`INI_INT()`,`INI_FLT()`,和`INI_BOOL()`這類宏很容易地轉換為標量值。
這個查詢和轉換過程非常低效的原因有兩個,它必須通過名字位于一個hash表之中,所以每次都要重新獲取。這種查找方式對于用戶空間腳本是非常好的,因為一個腳本只有在運行時才會被編譯,但是對于編譯型的語言,做這個工作是毫無意義的。
對于標量來說,這個會導致更加的低效,因為底層的字符串值在每一次被請求時候都必須再轉換一次。使用你已經知道的,你可以聲明一個線程安全的全局存儲介質,并在每次改變的時候使用新值的地址來更新它。然后,任何代碼都可以通過在你的線程安全的結構之中查找指針來訪問INI設置,這個可以利用編譯時優化。
在php_sample4.h文件中,在`MODULE_GLOBALS`結構中添加`const char *greeting;`,然后更新位于 sample4.c中的兩個方法:
````c
ZEND_INI_MH(php_sample4_modify_greeting)
{
/* Disallow empty greetings */
if (new_value_length == 0) {
return FAILURE;
}
SAMPLE4_G(greeting) = new_value;
return SUCCESS;
}
PHP_FUNCTION(sample4_hello_world)
{
php_printf("%s\n", SAMPLE4_G(greeting));
}
````
因為這是一個優化 INI 值存取的非常一般的方法,另一對宏是通過Zend引擎來導出的,將INI設置綁定到全局變量。(Because this is a common approach to optimizing INI access, another pair of macros is exported by the engine that handle binding INI settings to global variables)
````c
STD_PHP_INI_ENTRY_EX("sample4.greeting", "Hello World",
PHP_INI_ALL, OnUpdateStringUnempty, greeting,
zend_sample4_globals, sample4_globals,
php_sample4_display_greeting)
````
這個條目和剛剛你不需要的回調執行相同的工作。相反,他使用一個通用目的的修飾回調 `OnUpdateStringUnempty`,并且連同信息在存儲空間的存儲位置。為了允許空白的問候語,你可以簡單的指定 `OnUpdateString`修飾語,這樣就比`OnUpdateStringUnempty`方法簡單。
類似的方法,比如INI設置可能會被綁定到類似long,double和zend_bool之類的標量上。在你的php_sample4.h中添加三個條目到MODULE_GLOBALS 結構中:
````c
long mylong;
double mydouble;
zend_bool mybool;
````
現在使用`STD_PHP_INI_ENTRY()`宏在`PHP_INI_BEGIN()/PHP_INI_END()`塊中創建INI條目,不同于它的 `_EX`版本的僅僅是缺少一個播放者方法以及綁定他們到你的新值:
````c
STD_PHP_INI_ENTRY("sample4.longval", "123",
PHP_INI_ALL, OnUpdateLong, mylong,
zend_sample4_globals, sample4_globals)
STD_PHP_INI_ENTRY("sample4.doubleval", "123.456",
PHP_INI_ALL, OnUpdateDouble, mydouble,
zend_sample4_globals, sample4_globals)
STD_PHP_INI_ENTRY("sample4.boolval", "1",
PHP_INI_ALL, OnUpdateBool, mybool,
zend_sample4_globals, sample4_globals)
````
注意在這一點上,如果`DISPLAY_INI_ENTRIES()`被調用,"sample4.boolval" 這個布爾類型的INI設置會像其他的設置一樣以字符串被顯示出來;然而,優先以字符串輸出的是"on"或者“off”。為了確認這些顯示的值是有意義的,你可以在后面的兩個方法之中任選一個,其中一個是切換到`STD_PHP_INI_ENTRY_EX()`宏創建一個顯示的方法,另一個是你可以任意使用可以幫到你的宏:
````c
STD_PHP_INI_BOOLEAN("sample4.boolval", "1",
PHP_INI_ALL, OnUpdateBool, mybool,
zend_sample4_globals *, sample4_globals)
````
這種特定類型的宏跟在INI家族中的布爾是不一樣的,它僅僅提供一個顯示處理者來將開啟的設置為“on”,關閉的設置為"off"。
## links
* [目錄](<preface.md>)
* 13 [INI設置](<13.md>)
* 13.2 [小結](<13.2.md>)
- about
- 開始閱讀
- 目錄
- 1 PHP的生命周期
- 1.讓我們從SAPI開始
- 2.PHP的啟動與終止
- 3.PHP的生命周期
- 4.線程安全
- 5.小結
- 2 PHP變量在內核中的實現
- 1. 變量的類型
- 2. 變量的值
- 3. 創建PHP變量
- 4. 變量的存儲方式
- 5. 變量的檢索
- 6. 類型轉換
- 7. 小結
- 3 內存管理
- 1. 內存管理
- 2. 引用計數
- 3. 總結
- 4 動手編譯PHP
- 1. 編譯前的準備
- 2. PHP編譯前的config配置
- 3. Unix/Linux平臺下的編譯
- 4. 在Win32平臺上編譯PHP
- 5. 小結
- 5 Your First Extension
- 1. 一個擴展的基本結構
- 2. 編譯我們的擴展
- 3. 靜態編譯
- 4. 編寫函數
- 5. 小結
- 6 函數返回值
- 1. 一個特殊的參數:return_value
- 2. 引用與函數的執行結果
- 3. 小結
- 7 函數的參數
- 1. zend_parse_parameters
- 2. Arg Info 與類型綁定
- 3. 小結
- 8 使用HashTable與{數組}
- 1. 數組(C中的)與鏈表
- 2. 操作HashTable的API
- 3. 在內核中操作PHP語言中數組
- 4. 小結
- 9 PHP中的資源類型
- 1. 復合類型的數據——{資源}
- 2. Persistent Resources
- 3. {資源}自有的引用計數
- 4. 小結
- 10 PHP中的面向對象(一)
- 1. zend_class_entry
- 2. 定義一個類
- 3. 定義一個接口
- 4. 類的繼承與接口的實現
- 5. 小結
- 11 PHP中的面向對象(二)
- 1. 生成對象的實例與調用方法
- 2. 讀寫對象的屬性
- 3. 小結
- 12 啟動與終止的那點事
- 2. 小結
- 1. 關于生命周期
- 2. MINFO與phpinfo
- 3. 常量
- 4. PHP擴展中的全局變量
- 5. PHP語言中的超級全局變量
- 6. 小結
- 13 INI設置
- 1. 聲明和訪問ini設置
- 2. 小結
- 2. 小結
- 14 流式訪問
- 1. 概覽
- 2. 打開流
- 3. 訪問流
- 4. 靜態資源操作
- 5. 小結
- 15 流的實現
- 1. php流的表象之下
- 2. 包裝器操作
- 3. 實現一個包裝器
- 4. 操縱
- 5. 檢查
- 6. 小結
- 16 有趣的流
- 1. 上下文
- 2. 過濾器
- 3. 小結
- 17 配置和鏈接
- 1. autoconf
- 2. 庫的查找
- 3. 強制模塊依賴
- 4. Windows方言
- 5. 小結
- 18 擴展生成
- 1. ext_skel
- 2. PECL_Gen
- 3. 小結
- 19 設置宿主環境
- 1. 嵌入式SAPI
- 2. 構建并編譯一個宿主應用
- 3. 通過嵌入包裝重新創建cli
- 4. 老技術新用
- 5. 小結
- 20 高級嵌入式
- 1. 回調到php中
- 2. 錯誤處理
- 3. 初始化php
- 4. 覆寫INI_SYSTEM和INI_PERDIR選項
- 5. 捕獲輸出
- 6. 同時擴展和嵌入
- 7. 小結
- 約定