PHP 提供多種陣列合併函式,行為差異大,選錯易導致資料遺失或結構錯誤。本文深入對比 array_merge()、array_merge_recursive()、+ 運算子、array_replace()、array_replace_recursive() 的關鍵差異,並提供選型決策表。
$a = ['a' => 'apple', 'b' => 'banana'];
$b = ['b' => 'blueberry', 'c' => 'cherry'];
print_r(array_merge($a, $b));
/*
Array
(
[a] => apple
[b] => blueberry ← 字串鍵 'b' 被覆蓋
[c] => cherry
)
*/
// 數值鍵會重新索引 (0,1,2...)
$arr1 = [10 => 'x', 20 => 'y'];
$arr2 = [30 => 'z'];
print_r(array_merge($arr1, $arr2));
// Array ( [0] => x, [1] => y, [2] => z )
$a = ['color' => 'red', 'fruit' => 'apple'];
$b = ['color' => 'green', 'size' => 'large'];
print_r(array_merge_recursive($a, $b));
/*
Array
(
[color] => Array ( [0] => red, [1] => green ) ← 合併為陣列
[fruit] => apple
[size] => large
)
*/
$a = ['a' => 'apple', 'b' => 'banana'];
$b = ['b' => 'blueberry', 'c' => 'cherry'];
print_r($a + $b);
/*
Array
(
[a] => apple
[b] => banana ← 保留 $a 的值
[c] => cherry
)
*/
$a = [10 => 'x', 20 => 'y'];
$b = [20 => 'z', 30 => 'w'];
print_r(array_replace($a, $b));
// Array ( [10] => x, [20] => z, [30] => w ) ← 鍵 20 保留
$a = ['config' => ['debug' => true, 'log' => 'daily']];
$b = ['config' => ['debug' => false, 'cache' => 'redis']];
print_r(array_replace_recursive($a, $b));
/*
Array
(
[config] => Array
(
[debug] => false ← 覆蓋
[log] => daily ← 保留
[cache] => redis ← 新增
)
)
*/
| 需求 | 推薦函式 | 關鍵行為 |
|---|---|---|
| 一般合併,字串鍵後者勝出,數值鍵重新索引 | array_merge() | 最常用、行為直覺 |
| 字串鍵衝突需**保留所有值** (建立陣列) | array_merge_recursive() | 配置合併、多值欄位 |
| **保留第一個陣列的值**,忽略後者同鍵 | $a + $b | 預設值覆寫、優先級合併 |
| 保留數值鍵不重新索引,字串鍵後者勝出 | array_replace() | 鍵有業務含義 (如 ID) |
| 多維陣列深度合併,標量值覆蓋,陣列值遞迴合併 | array_replace_recursive() | 配置檔案多層覆寫 |
$default = [
'app' => ['debug' => false, 'locale' => 'en'],
'db' => ['host' => 'localhost', 'port' => 3306],
];
$env = [
'app' => ['debug' => true],
'db' => ['host' => 'db.prod'],
];
$local = [
'db' => ['port' => 3307],
];
// 深度合併,保留結構
$config = array_replace_recursive($default, $env, $local);
/*
[
'app' => ['debug' => true, 'locale' => 'en'],
'db' => ['host' => 'db.prod', 'port' => 3307],
]
*/
// $_POST['tags'] 可能來自多個來源
$source1 = ['tags' => ['php', 'laravel']];
$source2 = ['tags' => ['mysql']];
// 合併為單一陣列
$merged = array_merge_recursive($source1, $source2);
// ['tags' => ['php', 'laravel', 'mysql']]
// 若用 array_merge 會被覆蓋!
$wrong = array_merge($source1, $source2);
// ['tags' => ['mysql']] ← 資料遺失
$defaults = [
'page' => 1,
'per_page' => 15,
'sort' => 'created_at',
'order' => 'desc',
];
$userInput = ['page' => 3, 'sort' => 'name']; // 缺少 per_page, order
// 使用者輸入優先,缺少則用預設
$params = $userInput + $defaults;
// ['page' => 3, 'per_page' => 15, 'sort' => 'name', 'order' => 'desc']
array_merge_recursive 若深度巢狀極深,可能觸發遞迴限制// 錯誤:期望保留數值鍵
$arr1 = [100 => 'a'];
$arr2 = [200 => 'b'];
$result = array_merge($arr1, $arr2);
// 結果: [0 => 'a', 1 => 'b'] ← 鍵遺失!
// 正確: array_replace($arr1, $arr2) 或 $arr1 + $arr2
// 錯誤:多維陣列用 array_merge 導致子陣列被整個覆蓋
$config1 = ['db' => ['host' => 'a', 'port' => 3306]];
$config2 = ['db' => ['port' => 3307]];
array_merge($config1, $config2);
// ['db' => ['port' => 3307]] ← host 遺失!
// 正確: array_replace_recursive($config1, $config2)
記住三大核心行為:
1.array_merge= 重新索引 + 字串鍵覆蓋
2.array_merge_recursive= 字串鍵衝突變陣列
3.+= 第一優先,保留原鍵
根據「衝突時要保留誰、鍵是否有意義、是否多層」三個問題選型。