### 7.5.2 php.ini配置
php.ini是PHP主要的配置文件,解析時PHP將在這些地方依次查找該文件:當前工作目錄、環境變量PHPRC指定目錄、編譯時指定的路徑,在命令行模式下,php.ini的查找路徑可以用`-c`參數替代。
該文件的語法非常簡單:`配置標識符 = 值`。空白字符和用分號';'開始的行被忽略,[xxx]行也被忽略;配置標識符大寫敏感,通常會用'.'區分不同的節;值可以是數字、字符串、PHP常量、位運算表達式。
關于php.ini的解析過程本節不作介紹,只從應用的角度介紹如何在一個擴展中獲取一個配置項,通常會把php.ini的配置映射到一個變量,從而在使用時直接讀取那個變量,也就是把所有的配置轉化為了C語言中的變量,擴展中一般會把php.ini配置映射到上一節介紹的全局變量(資源),要想實現這個轉化需要在擴展中為每一項配置設置映射規則:
```c
PHP_INI_BEGIN()
//每一項配置規則
...
PHP_INI_END();
```
這兩個宏實際只是把各配置規則組成一個數組,配置規則通過`STD_PHP_INI_ENTRY()`設置:
```c
STD_PHP_INI_ENTRY(name,default_value,modifiable,on_modify,property_name,struct_type,struct_ptr)
```
* __name:__ php.ini中的配置標識符
* __default_value:__ 默認值,注意不管轉化后是什么類型,這里必須設置為字符串
* __modifiable:__ 可修改等級,ZEND_INI_USER為可以在php腳本中修改,ZEND_INI_SYSTEM為可以在php.ini中修改,還有一個ZEND_INI_PERDIR,ZEND_INI_ALL表示三種都可以,通常情況下設置為ZEND_INI_ALL、ZEND_INI_SYSTEM即可
* __on_modify:__ 函數指針,用于指定發現這個配置后賦值處理的函數,默認提供了5個:OnUpdateBool、OnUpdateLong、OnUpdateLongGEZero、OnUpdateReal、OnUpdateString、OnUpdateStringUnempty,支持可以自定義
* __property_name:__ 要映射到的結構struct_type中的成員
* __struct_type:__ 映射結構的類型
* __struct_ptr:__ 映射結構的變量地址,發現配置后會
> 除了STD_PHP_INI_ENTRY()這個宏還有一個類似的宏`STD_PHP_INI_BOOLEAN()`,用法一致,差別在于后者會自動把配置添加到phpinfo()輸出中。
這個宏展開后生成一個`zend_ini_entry_def`結構:
```c
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,則可以這么定義:
```c
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都是宏展開后的實際值,前者是結構體類型,后者是具體分配的變量,上面的定義展開后:
```c
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 <stdio.h>
> #include <stddef.h>
>
> 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()`處理,整體的流程:
```c
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()`賦值處理:
```c
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映射到全局變量的完整代碼:
```c
//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);
```
```c
//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語言變量中,總結下主要分為兩步:
* __定義解析規則:__ 通過PHP_INI_BEGIN()、PHP_INI_END()、STD_PHP_INI_ENTRY()配置
* __執行規則映射:__ 由REGISTER_INI_ENTRIES()來完成,這個操作之后解析目的變量就可以使用了
- 前言
- 第1章 PHP基本架構
- 1.1 PHP簡介
- 1.2 PHP7的改進
- 1.3 FPM
- 1.3.1 概述
- 1.3.2 基本實現
- 1.3.3 FPM的初始化
- 1.3.4 請求處理
- 1.3.5 進程管理
- 1.4 PHP執行的幾個階段
- 第2章 變量
- 2.1 變量的內部實現
- 2.2 數組
- 2.3 靜態變量
- 2.4 全局變量
- 2.5 常量
- 第3章 Zend虛擬機
- 3.1 PHP代碼的編譯
- 3.1.1 詞法解析、語法解析
- 3.1.2 抽象語法樹編譯流程
- 3.2 函數實現
- 3.2.1 內部函數
- 3.2.2 用戶函數的實現
- 3.3 Zend引擎執行流程
- 3.3.1 基本結構
- 3.3.2 執行流程
- 3.3.3 函數的執行流程
- 3.3.4 全局execute_data和opline
- 3.4 面向對象實現
- 3.4.1 類
- 3.4.2 對象
- 3.4.3 繼承
- 3.4.4 動態屬性
- 3.4.5 魔術方法
- 3.4.6 類的自動加載
- 3.5 運行時緩存
- 3.6 Opcache
- 3.6.1 opcode緩存
- 3.6.2 opcode優化
- 3.6.3 JIT
- 第4章 PHP基礎語法實現
- 4.1 類型轉換
- 4.2 選擇結構
- 4.3 循環結構
- 4.4 中斷及跳轉
- 4.5 include/require
- 4.6 異常處理
- 第5章 內存管理
- 5.1 Zend內存池
- 5.2 垃圾回收
- 第6章 線程安全
- 6.1 什么是線程安全
- 6.2 線程安全資源管理器
- 第7章 擴展開發
- 7.1 概述
- 7.2 擴展的實現原理
- 7.3 擴展的構成及編譯
- 7.3.1 擴展的構成
- 7.3.2 編譯工具
- 7.3.3 編寫擴展的基本步驟
- 7.3.4 config.m4
- 7.4 鉤子函數
- 7.5 運行時配置
- 7.5.1 全局變量
- 7.5.2 ini配置
- 7.6 函數
- 7.6.1 內部函數注冊
- 7.6.2 函數參數解析
- 7.6.3 引用傳參
- 7.6.4 函數返回值
- 7.6.5 函數調用
- 7.7 zval的操作
- 7.7.1 新生成各類型zval
- 7.7.2 獲取zval的值及類型
- 7.7.3 類型轉換
- 7.7.4 引用計數
- 7.7.5 字符串操作
- 7.7.6 數組操作
- 7.8 常量
- 7.9 面向對象
- 7.9.1 內部類注冊
- 7.9.2 定義成員屬性
- 7.9.3 定義成員方法
- 7.9.4 定義常量
- 7.9.5 類的實例化
- 7.10 資源類型
- 7.11 經典擴展解析
- 7.8.1 Yaf
- 7.8.2 Redis
- 第8章 命名空間
- 8.1 概述
- 8.2 命名空間的定義
- 8.2.1 定義語法
- 8.2.2 內部實現
- 8.3 命名空間的使用
- 8.3.1 基本用法
- 8.3.2 use導入
- 8.3.3 動態用法
- 附錄
- break/continue按標簽中斷語法實現
- defer推遲函數調用語法的實現
- 一起線上事故引發的對PHP超時控制的思考