網路上流傳的「取得變數名稱」函數存在嚴重 Bug:使用寬鬆比較 (==) 導致字串 "hello" 與整數 0 判定相等。本文深度分析問題根因並提供修正版。
<?php
function getVarNameBuggy(&$src) {
$save = $src;
$allVars = $GLOBALS;
foreach ($allVars as $k => $v) {
if ($src == $v) { // ❌ 寬鬆比較: "hello" == 0 為 true
$src = 'change';
if ($src == $GLOBALS[$k]) {
$src = $save;
return $k;
}
}
}
return null;
}
// 測試
$test2 = "hello";
$countNum = 0;
echo getVarNameBuggy($test2); // 輸出 "countNum" (錯誤!應為 "test2")
?>
<?php
// PHP 型別轉換規則導致:
var_dump("hello" == 0); // true (字串轉整數 = 0)
var_dump("hello" == "0"); // false
var_dump("0" == 0); // true
var_dump("hello" === 0); // false (嚴格比較)
// 當 $src = "hello",遍歷到 $countNum = 0 時:
// $src == $v → "hello" == 0 → true ❌
// 導致錯誤匹配第一個整數 0 變數
?>
<?php
function getVarName(&$src): ?string {
$save = $src;
$allVars = $GLOBALS;
foreach ($allVars as $k => $v) {
// ✅ 嚴格比較:型別與值皆需相同
if ($src === $v) {
$src = '___CHANGED_MARKER___'; // 唯一標記值
if ($src === $GLOBALS[$k]) {
$src = $save;
return $k;
}
$src = $save; // 還原並繼續(處理多個同值變數)
}
}
return null;
}
// 進階版:回傳所有同值變數名
function getAllVarNames(&$src): array {
$names = [];
$save = $src;
$allVars = $GLOBALS;
foreach ($allVars as $k => $v) {
if ($src === $v) {
$src = '___CHANGED_MARKER___';
if ($src === $GLOBALS[$k]) {
$names[] = $k;
}
$src = $save;
}
}
return $names;
}
?>
&$src 為引用傳遞,修改 $src 會影響原變數$GLOBALS[$k] 指向同一個變數容器(Zval)$src === $GLOBALS[$k] 修改後同步變化,證明為同一變數| 限制 | 說明 |
|---|---|
| 無法區分同值變數 | $a = 1; $b = 1; 兩者值相同,無法分辨誰是誰 |
| 僅限全域變數 | 區域變數不在 $GLOBALS 中 |
| 效能較差 | 遍歷所有全域變數,大型專案慎用 |
| 修改標記值衝突 | 極少數情況下標記值可能與現有資料相同 |
<?php
// 1. 除錯用:debug_zval_dump()
$a = [1,2,3];
debug_zval_dump($a); // 顯示 refcount、型別、值
// 2. 物件屬性名:ReflectionClass
class User { public $name = 'John'; }
$user = new User();
$ref = new ReflectionClass($user);
foreach ($ref->getProperties() as $prop) {
echo $prop->getName() . "\n"; // "name"
}
// 3. 陣列鍵名:array_keys()
$data = ['id' => 1, 'name' => 'John'];
print_r(array_keys($data)); // ['id', 'name']
// 4. 變數名稱本身不重要 → 重構程式碼結構
// 不要依賴「取得變數名稱」這類反模式
?>
核心教訓:永遠使用 === 除非明確需要型別轉換。PHP 的型別轉換規則複雜且反直覺,寬鬆比較是 Bug 溫床。