HTTP Request
↓
Apache SAPI (mod_php / php-fpm)
↓
php_request_startup()
↓
php_hash_environment() ← 關鍵入口
↓
sapi_module.treat_data() (php_default_treat_data)
↓
php_register_variable_safe()
↓
php_register_variable_ex() ← 變量名規範化 (點號→下劃線)
↓
填充 $_GET, $_POST, $_COOKIE, $_SERVER, $_ENV, $_FILES
php_hash_environment() 初始化自動全局變量struct auto_global_record {
char *name; // "_GET"
uint name_len;
char *long_name; // "HTTP_GET_VARS" (已移除 PHP 5.4+)
uint long_name_len;
zend_bool jit_initialization;
} auto_global_records[] = {
{"_GET", ..., "HTTP_GET_VARS", ..., 0},
{"_POST", ..., "HTTP_POST_VARS", ..., 0},
{"_COOKIE", ..., "HTTP_COOKIE_VARS", ..., 0},
{"_SERVER", ..., "HTTP_SERVER_VARS", ..., 1}, // JIT
{"_ENV", ..., "HTTP_ENV_VARS", ..., 1}, // JIT
{"_FILES", ..., "HTTP_POST_FILES", ..., 0},
};
variables_order (默認 "GPCS") 依次處理 GET/POST/COOKIE/SERVER/ENV。jit_initialization=1 表示惰性初始化:首次訪問 $_SERVER 時才填充。sapi_module.treat_data() 解析輸入// 簡化版 php_default_treat_data
switch (arg) {
case PARSE_POST:
if (content_type == application/x-www-form-urlencoded)
parse_str(raw_post_data, &array);
else if (content_type == multipart/form-data)
sapi_module.read_post() → rfc1867_post_handler() → 填充 $_POST/$_FILES;
break;
case PARSE_GET:
parse_str(query_string, &array); // $_SERVER['QUERY_STRING']
break;
case PARSE_COOKIE:
parse_str(cookie_header, &array); // 分號分隔
break;
}
php_register_variable_ex() 核心規範化邏輯 (回答「點號變下劃線」)PHPAPI void php_register_variable_ex(char *var, zval *val, zval *track_vars_array) {
// 1. 去除前導空格
while (*var && *var == ' ') var++;
// 2. 關鍵:將空格和點號替換為下劃線
for (char *p = var; *p; p++) {
if (*p == ' ' || *p == '.') {
*p = '_'; // ← 這裡!表單 name="a.b" 變為 $_POST['a_b']
} else if (*p == '[') {
is_array = 1; // 識別陣列語法 name="arr[key]"
}
}
// 3. 解析陣列語法 a[b][c] → 多維陣列
// 4. 將值插入 track_vars_array (即 $_GET/$_POST 等)
}
[a-zA-Z_\x80-\xff][a-zA-Z0-9_\x80-\xff]*,**不允許點號**。name="user.name",PHP 為兼容自動轉換。name="a.b" 和 name="a_b" 會衝突,無法區分。variables_order 控制生成順序與覆蓋規則; php.ini
variables_order = "GPCS" ; 默認
; 處理順序:G → P → C → S
; 後處理的會覆蓋先處理的同名鍵 (若 register_globals=On)
; 現代 PHP (register_globals=Off) 僅影響超全局變量內部順序,不影響用戶代碼訪問。
$HTTP_*_VARS 長名稱變量。auto_globals_jit 默認開啟:$_SERVER/$_ENV 首次使用時才填充,節省啟動開銷。$_REQUEST 由 request_order 控制 (默認 "GP"),不再包含 Cookie。<?php
// 僅 CLI 測試用,勿用於生產
function mockSuperGlobals(array $get=[], array $post=[], array $cookie=[]) {
$_GET = $get;
$_POST = $post;
$_COOKIE = $cookie;
$_REQUEST = array_merge($_GET, $_POST); // 依 request_order
$_SERVER['REQUEST_METHOD'] = 'POST';
$_SERVER['QUERY_STRING'] = http_build_query($get);
$_SERVER['HTTP_COOKIE'] = http_build_query($cookie, '', '; ');
}
?>
超全局變量生成鏈路:SAPI → php_hash_environment → treat_data → register_variable_ex (點號轉下劃線) → 填充符號表。理解此流程有助於排查「為什麼 $_POST 為空」(Content-Type 錯誤)、「為什麼鍵名變了」(點號轉換)、「為什麼 $_SERVER 延遲初始化」(JIT)。