設定 $_COOKIE['my.name'] = 1 後,實際讀取卻必須用 $_COOKIE['my_name']。這不是 Bug,是 PHP 為了相容舊版 register_globals 特意設計的「自動轉換」行為。本文深入解析原因、影響與現代最佳實踐。
<?php
setcookie('user.name', 'John', time()+3600);
// 瀏覽器發送 Cookie: user.name=John
// PHP 端接收
var_dump($_COOKIE['user.name']); // NULL ❌
var_dump($_COOKIE['user_name']); // 'John' ✅
// $_REQUEST、$_GET、$_POST 同理:鍵名中 .、空格、[ ] 等皆會轉為 _
// 例如 → $_POST['a_b']
register_globals 的惡夢PHP 4.2 以前預設開啟 register_globals = On,會將 EGPCS(Environment、Get、Post、Cookie、Server)變數直接註冊為全域變數:
// URL: ?user.name=John
// PHP 內部自動建立 $user_name = 'John'; // 合法變數名
// 若保留點號 → $user.name 會導致語法錯誤
為了讓「自動全域變數」能正常運作,PHP 強制將所有超全域陣列鍵名中的 非字母數字底線字元統一轉為底線。雖然 register_globals 已於 PHP 5.4 移除,但相容性行為保留至今。
register_globals 是災難<?php
// 假設開啟 register_globals
$isAdmin = false;
// 攻擊者請求 ?isAdmin=1
// → $isAdmin 變成 true,繞過權限檢查!
這類「變數汙染」漏洞極難追蹤,現代框架(Laravel、Symfony)皆在啟動期強制 ini_set('register_globals', 'Off') 並清理 $GLOBALS。
[a-zA-Z0-9_],避免點號、連字號、空格、中括號。getCookie('user.name') 內部自動 str_replace('.', '_', $key)。php.ini:request_order = "GP"(忽略 Cookie 覆蓋 POST/GET),variables_order = "GPCS"。Set-Cookie: ...; HttpOnly; Secure; SameSite=Lax 降低 XSS/CSRF 風險。| 原始鍵名 | PHP 實際鍵名 | 建議寫法 |
|---|---|---|
| user.name | user_name | user_name |
| data[0] | data_0_ | data_0 |
| my-key | my_key | my_key |
| a b | a_b | a_b |
結論:了解歷史包袱,乾淨命名,才能避免莫名其妙的「讀不到 Cookie」除錯浪費時間。