← PHP array_replace()函数:用后续数组覆盖前一个数组的实战教程 没有下一篇了 →

PHP array_replace_recursive()函数:递归替换多维数组值的实战教程

著
原创 2026-10-08 PHP 已有人查阅

一、为什么需要递归版本

array_replace()处理单层数组没问题,一旦数组嵌套,它会把整个子数组当成一个值整体替换掉。实际项目里的配置、表单数据、API响应结构,往往是多层嵌套的,只想改其中某一个叶子节点,不想把整棵子树都换掉。

array_replace_recursive()就是解决这个问题的。它沿着键名往下走,遇到两边都是数组的键,继续递归;遇到不是数组的值,才执行覆盖。这样改哪一层就只动哪一层。

二、函数原型与参数

array_replace_recursive(array $array1, array ...$replacements): array
参数 说明 是否必需
array1 被替换的基准数组 必需
replacements 一个或多个替换数组 可选

返回值:替换后的新数组。参数中存在非数组时返回null。原数组不受影响。

替换数组可以有多个,后面的数组优先级更高,会覆盖前面数组在同一路径上的值。

三、递归替换的判定规则

核心逻辑可以用三步概括:

  1. 遍历替换数组的每个键。

  2. 如果基准数组和替换数组在同一个键上都是数组,进入下一层递归。

  3. 否则,用替换数组的值覆盖基准数组同键的值;键不存在则追加。

这里的关键是第二条。两边的值必须同时是数组才会递归,只要有一边不是数组,就直接覆盖。看例子:

<?php
$array1 = array("a" => array("apple"), "b" => array("ball"));
$array2 = array("a" => array("all"), "b" => array("black"));

print_r(array_replace_recursive($array1, $array2));

输出:

Array
(
    [a] => Array
        (
            [0] => all
        )

    [b] => Array
        (
            [0] => black
        )
)

键a和b在两边都是数组,进入递归。子数组里键0对应的值不是数组,执行覆盖,所以apple变成all,ball变成black。

四、长度不等的子数组怎么处理

这是array_replace_recursive()里值得单独拿出来讲的地方。递归到子数组后,数字键的处理是"同键覆盖、无键保留原值"。

<?php
$color1 = array("a" => array("blue"), "b" => array("green", "red"));
$color2 = array("a" => array("yellow"), "b" => array("black"));
$color3 = array("a" => array("saffron"), "b" => array("pink"));

print_r(array_replace_recursive($color1, $color2, $color3));

输出:

Array
(
    [a] => Array
        (
            [0] => saffron
        )

    [b] => Array
        (
            [0] => pink
            [1] => red
        )
)

拆开看键b的变化:

  • $color1的b是["green","red"],下标0和1。

  • $color2的b是["black"],只有下标0,把green覆盖成black,下标1的red保留。

  • $color3的b是["pink"],把下标0的black覆盖成pink,下标1的red依然保留。

最终b是["pink","red"]。替换数组的元素个数少于原数组时,原数组多出来的元素不会被删除,会保留下来。这一点和array_replace()单层行为一致,但嵌套时容易被忽略。

再看一个类似例子:

<?php
$player1 = array("a" => array("sachin"), "b" => array("ganguly", "sehwag"));
$player2 = array("a" => array("yuvraj"), "b" => array("ishant"));

print_r(array_replace_recursive($player1, $player2));

输出:

Array
(
    [a] => Array
        (
            [0] => yuvraj
        )

    [b] => Array
        (
            [0] => ishant
            [1] => sehwag
        )
)

b的0号被ishant覆盖,1号的sehwag因为替换数组没有该下标,保留下来。

五、与相关函数的差异

函数 递归 数字键处理 字符串键处理 适用场景
array_replace() 否 同键覆盖,不重排 同键覆盖 单层数组替换
array_replace_recursive() 是 子数组内同键覆盖,不重排 逐层覆盖 多层配置合并
array_merge_recursive() 是 同键合并成数组 同键合并成数组 需要保留双方值
array_merge() 否 重新编号追加 同键覆盖 列表合并

要特别注意array_replace_recursive()和array_merge_recursive()的区别。前者是"覆盖",后者是"合并"。如果两边同一个键都是数组,array_merge_recursive()会把两个子数组的元素拼在一起,而不是替换。配置覆盖场景要的是替换,用array_replace_recursive()才对。

个人经验:早期做一个后台主题配置功能,默认主题色和用户自定义色都是嵌套数组。本来想用array_merge_recursive()合并,结果用户只改了按钮颜色,两个颜色值被合并成了一个数组,前端拿到["#fff","#000"]这种结构直接渲染异常。换成array_replace_recursive()后,用户改哪个叶子节点就覆盖哪个,其余保持默认。"合并"和"覆盖"是两种语义,配置场景几乎都是覆盖。

六、项目实例

实例一:代码号学习编程·多层配置逐项覆盖

<?php
$default = [
    'db' => [
        'host' => 'localhost',
        'port' => 3306,
        'charset' => 'utf8mb4',
    ],
    'cache' => [
        'driver' => 'file',
        'ttl' => 3600,
    ],
];

$userConfig = [
    'db' => [
        'host' => '192.168.1.10',
        'port' => 3307,
    ],
];

print_r(array_replace_recursive($default, $userConfig));

输出:

Array
(
    [db] => Array
        (
            [host] => 192.168.1.10
            [port] => 3307
            [charset] => utf8mb4
        )

    [cache] => Array
        (
            [driver] => file
            [ttl] => 3600
        )
)

用户只改了host和port,charset保留默认,cache整块不动。这正是递归替换想要的效果。

实例二:代码号学习编程·多语言文案分层覆盖

<?php
$lang1 = array("a" => array("C"), "b" => array("Java", "PHP"));
$lang2 = array("a" => array("Perl"), "b" => array("HTML"));

print_r(array_replace_recursive($lang1, $lang2));

输出:

Array
(
    [a] => Array
        (
            [0] => Perl
        )

    [b] => Array
        (
            [0] => HTML
            [1] => PHP
        )
)

a组的下标0被Perl覆盖,b组的下标0被HTML覆盖,下标1的PHP保留。这个行为在覆盖默认语言包时很常见:只替换翻译过的条目,没翻译的继续用默认值。

实例三:代码号学习编程·表单默认值与提交值合并

<?php
$formDefault = [
    'profile' => [
        'nickname' => '',
        'city' => '',
        'tags' => ['php', 'mysql'],
    ],
];

$formInput = [
    'profile' => [
        'nickname' => '代码号',
        'tags' => ['redis'],
    ],
];

print_r(array_replace_recursive($formDefault, $formInput));

输出:

Array
(
    [profile] => Array
        (
            [nickname] => 代码号
            [city] => 
            [tags] => Array
                (
                    [0] => redis
                    [1] => mysql
                )
        )
)

tags数组里0号被覆盖,1号保留。这里要留意:如果业务希望tags以用户提交为准,递归替换反而不合适,因为残留的mysql不是用户想要的结果。这种"数组整体替换"的需求,单层array_replace()或直接赋值更稳妥。

七、踩坑记录

坑一:数字子数组残留元素。替换数组的子数组比原数组短时,原数组多出来的元素不会被清掉。列表语义的数据用递归替换,会拿到意料之外的尾部元素。

坑二:和array_merge_recursive()混用。想要覆盖结果却用了合并函数,同键数组被拼在一起,结构变了。认准"覆盖"这个语义。

坑三:以为能删除键。array_replace_recursive()只做替换和追加,不能通过传空值来删除某个键。要删除键得用unset或array_diff_key。

坑四:参数顺序写反。array_replace_recursive($override,$base)和array_replace_recursive($base,$override)效果相反。基准数组放第一个,覆盖数组放后面。

坑五:传入非数组参数。有非数组参数时返回null,后续操作null会出错。调用前应做类型检查。

坑六:深层递归的性能。嵌套层数很深、数组规模很大时,递归开销会体现出来。配置合并这类低频操作没问题,高频循环里应避免反复递归替换。

八、本节课程知识要点

  1. array_replace_recursive()沿键名逐层递归,两边同为数组才继续往下,否则覆盖。

  2. 子数组内数字键同键覆盖,替换数组没有的下标保留原值,不会删除。

  3. 多个替换数组时,靠后的优先级更高。

  4. 与array_merge_recursive()语义不同,前者是覆盖,后者是合并。

  5. 返回新数组,原数组不变;参数非法时返回null。

  6. 列表语义的子数组不适合递归替换,容易残留尾部元素。

九、常见问题

问:它和array_replace()最根本的区别是什么?

array_replace()只处理一层,遇到子数组整体替换;array_replace_recursive()会进入子数组继续按键替换。

问:子数组里的元素会被删掉吗?

不会。替换数组没有覆盖到的下标,原值保留。

问:能不能用它删除某个键?

不能。它只做替换和追加。删除键要用unset()或array_diff_key()。

问:替换数组里的值是null会怎样?

null不是数组,会直接覆盖对应键的值,键变成null,不会触发递归。

问:多层配置合并该选哪个?

要"用户改了才覆盖、没改保留默认",用array_replace_recursive()。要"两边值都保留",才考虑array_merge_recursive()。

← PHP array_replace()函数:用后续数组覆盖前一个数组的实战教程 没有下一篇了 →
分享笔记 (共有 篇笔记)
验证码:
微信公众号