PHP 陣列鍵名支援 int 與 string 兩種類型,但隱性型別轉換規則極易導致難以排查的 Bug。本文系統梳理鍵名轉換規則、特殊值行為與最佳實踐。
| 原始鍵名 | 實際鍵名 | 轉換規則 | 訪問方式 |
|---|---|---|---|
true | 1 | 布爾轉整數 | $arr[1] 或 $arr[true] |
false | 0 | 布爾轉整數 | $arr[0] 或 $arr[false] |
null | '' (空字串) | Null 轉空字串 | $arr[''] 或 $arr[null] |
123.45 | 123 | 浮點數截取整數部分 | $arr[123] 或 $arr[123.45] |
'123' | 123 | 數值字串轉整數 | $arr[123] 或 $arr['123'] |
'0123' | '0123' | 前導零字串保持字串 | $arr['0123'] |
-123 | -123 | 負整數保持 | $arr[-123] |
-123.45 | -123 | 負浮點數截取整數 | $arr[-123] |
<?php
$arr = [];
// 1. 布爾鍵名
$arr[true] = 'bool true';
$arr[false] = 'bool false';
echo "true 鍵實際: "; var_dump(array_keys($arr)); // [1, 0]
// 2. Null 鍵名
$arr[null] = 'null key';
echo "null 鍵實際: "; var_dump(array_keys($arr)); // [1, 0, '']
// 3. 浮點數鍵名
$arr[123.45] = 'float key';
echo "123.45 鍵實際: "; var_dump(array_keys($arr)); // [1, 0, '', 123]
// 4. 數值字串
$arr['456'] = 'string number';
echo "'456' 鍵實際: "; var_dump(array_keys($arr)); // [1, 0, '', 123, 456]
// 5. 前導零字串
$arr['0789'] = 'leading zero';
echo "'0789' 鍵實際: "; var_dump(array_keys($arr)); // [1, 0, '', 123, 456, '0789']
// 6. 訪問測試
var_dump($arr[true] === $arr[1]); // true
var_dump($arr[null] === $arr['']); // true
var_dump($arr[123.45] === $arr[123]); // true
var_dump($arr['456'] === $arr[456]); // true
var_dump($arr['0789'] === $arr[789]); // false! 關鍵差異
// 7. 遍歷時的鍵名
foreach ($arr as $key => $value) {
echo "Key: "; var_dump($key); // 遍歷顯示實際存儲的鍵名類型
}
$arr[] = $value$arr = ['a', 'b']; // 索引 0, 1
$arr[] = 'c'; // 索引 2
$arr[] = 'd'; // 索引 3
// 關鍵:取最大整數索引 + 1,非連續也不影響
$arr = [5 => 'A', 8 => 'B'];
$arr[] = 'C'; // 索引 9 (max(5,8)+1)
$arr[] = 'D'; // 索引 10
// 負索引不影響自動索引
$arr = [-3 => 'X'];
$arr[] = 'Y'; // 索引 0 (max(-3)+1 = 0? 不,PHP 取最大非負索引+1,無則從 0 開始)
// 實際:索引 0
// 字串索引不影響
$arr = ['foo' => 'bar'];
$arr[] = 'baz'; // 索引 0
規則總結: $arr[] 使用 max(array_keys($arr) 中的整數鍵) + 1,若無整數鍵則從 0 開始。
$settings = [];
$settings[true] = 'enabled';
$settings[false] = 'disabled';
$settings[1] = 'user_id_1'; // 覆蓋了 'enabled'!
$settings[0] = 'user_id_0'; // 覆蓋了 'disabled'!
// 正確:使用字串鍵名
$settings['enabled'] = true;
$settings['disabled'] = false;
$prices = [10.5, 20.3, 30.7];
$indexed = [];
foreach ($prices as $price) {
$key = $price * 10; // 105, 203, 307 - 看似整數
$indexed[$key] = $price;
}
// 實際:浮點數精度問題可能導致 104.9999999 → 索引 104
// 修正:
$key = (int)round($price * 10);
in_array / array_search 的鬆散比較$arr = [0 => 'apple', 1 => 'banana'];
in_array('apple', $arr); // true
in_array('0', $arr); // true! '0' == 0
in_array(false, $arr); // true! false == 0
// 嚴格模式
in_array('0', $arr, true); // false
in_array(false, $arr, true); // false
array_merge 重組數值索引$a = [5 => 'A', 8 => 'B'];
$b = [2 => 'C'];
$merged = array_merge($a, $b); // [0=>A, 1=>B, 2=>C] 原索引丟失!
// 保留索引用 +
$merged = $a + $b; // [5=>A, 8=>B, 2=>C]
$arr['user_id'],避免隱性轉換(string)$key 或 (int)$key'123' 會轉為 123,'0123' 保持字串,語義不一致foreach ($arr as $key => $val) 中 $key 類型可能混雜+,重組索引用 array_mergejson_encode 會將整數鍵名輸出為字串(JS 物件鍵名必須是字串)<?php
class TypedArray implements ArrayAccess, IteratorAggregate, Countable
{
private array $data = [];
private string $keyType; // 'int' | 'string'
public function __construct(string $keyType = 'string')
{
$this->keyType = $keyType;
}
public function offsetSet(mixed $key, mixed $value): void
{
$key = $this->normalizeKey($key);
$this->data[$key] = $value;
}
public function offsetGet(mixed $key): mixed
{
$key = $this->normalizeKey($key);
return $this->data[$key] ?? null;
}
private function normalizeKey(mixed $key): int|string
{
return match ($this->keyType) {
'int' => (int)$key,
'string' => (string)$key,
default => $key,
};
}
// 實作 ArrayAccess, IteratorAggregate, Countable 其他方法...
}
// 使用
$intArr = new TypedArray('int');
$intArr[123.45] = 'value'; // 自動轉為 123
$intArr['456'] = 'value2'; // 自動轉為 456
$strArr = new TypedArray('string');
$strArr[123] = 'value'; // 自動轉為 '123'
$strArr[null] = 'value2'; // 自動轉為 ''
理解 PHP 陣列鍵名的隱性轉換規則,是寫出健壯、可預測代碼的基礎。建立「鍵名類型顯式化」的編碼習慣,能從根源上消除一大類難以復現的邏輯錯誤。