php array_merge_recursive() 对同名数字键会创建新数组而非覆盖,需手写 deep_merge() 实现“后写入优先”的递归覆盖合并,正确处理字符串键递归、数字键追加、类型判断及空值安全。

PHP array_merge_recursive() 会把同名数字键变成新数组,不是你想要的“覆盖式递归合并”
很多人一搜“PHP递归合并数组”,第一反应是用 array_merge_recursive(),结果发现:['a' => 1] 和 ['a' => 2] 合并后变成 ['a' => [1, 2]] —— 这不是覆盖,是堆叠。你要的是后者值覆盖前者(尤其在配置合并、API响应补全等场景),得自己写逻辑。
核心思路:遍历第二个数组,对每个键判断目标位置是否为数组且源位置也是数组,是则递归调用;否则直接赋值(即“后写入优先”)。
常见错误现象:
- 直接用 array_merge():只做一层合并,嵌套数组被整个替换掉
- 忘记检查类型,导致 is_array() 判错(比如 null 或对象)
- 没处理数字键(0, 1),误当作字符串键处理,造成意外追加
- 只对
string键做递归判断;int键一律按非递归方式追加(避免索引错乱) - 必须显式用
is_array($arr1[$key]) && is_array($arr2[$key])双重判断,不能只判$arr2[$key] - 注意传参顺序:
deep_merge($base, $overlay)表示用$overlay覆盖$base
手写 deep_merge() 函数时,必须处理好引用和拷贝问题
PHP 数组默认传值,但递归过程中若直接修改原数组变量,可能引发意外副作用——尤其当某个子数组被多次引用时。稳妥做法是始终返回新数组,不修改输入。
性能影响:每次递归都 array_merge() 或循环赋值会产生临时数组,大数据量嵌套(如 >5 层、每层 >100 项)会有明显开销。可考虑用 foreach + isset() 替代 array_key_exists() 提速。
一个轻量可靠的实现:
function deep_merge(array $arr1, array $arr2): array
{
$result = $arr1;
foreach ($arr2 as $key => $value) {
if (is_int($key)) {
$result[] = $value;
} elseif (isset($result[$key]) && is_array($result[$key]) && is_array($value)) {
$result[$key] = deep_merge($result[$key], $value);
} else {
$result[$key] = $value;
}
}
return $result;
}
这个版本不依赖引用、不修改原数组、跳过数字键递归,兼容 PHP 7.4+。
用 array_replace_recursive()?小心它不处理缺失键的深层初始化
array_replace_recursive() 看似是官方替代方案,但它有个关键限制:只替换“已存在”的键,不会把 $arr2 中某层的全新子数组“注入”到 $arr1 的空位置。例如:
$a = ['db' => ['host' => 'localhost']];$b = ['db' => ['port' => 3306], 'redis' => ['host' => '127.0.0.1']];array_replace_recursive($a, $b) 结果中 'redis' 会丢失 —— 因为 $a 根层没有 'redis' 键。
所以它只适合“补全已有结构”,不适合“合并异构配置”。真要图省事又需健壮性,建议封装一层:
function safe_deep_merge(array $arr1, array $arr2): array
{
// 先确保根层所有键都存在
$keys = array_unique(array_merge(array_keys($arr1), array_keys($arr2)));
$result = [];
foreach ($keys as $k) {
if (isset($arr1[$k]) && isset($arr2[$k]) && is_array($arr1[$k]) && is_array($arr2[$k])) {
$result[$k] = safe_deep_merge($arr1[$k], $arr2[$k]);
} elseif (isset($arr2[$k])) {
$result[$k] = $arr2[$k];
} else {
$result[$k] = $arr1[$k];
}
}
return $result;
}
多维合并时,最容易被忽略的是类型安全和空值处理
实际项目里,你常遇到 null、''、[]、0 这些值混在配置中。比如 API 返回 'timeout' => null,你想用默认值覆盖,但 isset($val) 会把它当“不存在”而跳过。
这时候不能只靠 isset() 或 array_key_exists(),得结合 array_key_exists() + 显式类型判断:
- 用
array_key_exists($key, $arr1)判断键是否存在(包括null值) - 再用
is_array()单独判断值类型,避免把0或false当数组 - 如果业务允许覆盖空字符串或
null,就去掉isset(),直接走array_key_exists()分支
真正难的不是写几行递归,而是想清楚:“哪些空值该保留,哪些该被覆盖,哪些键根本不能递归”。这些逻辑一旦写死在函数里,后面改起来比重写还麻烦。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











