Skip to content

Instantly share code, notes, and snippets.

@dsantuc
Created March 6, 2020 22:07
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save dsantuc/aec254b82cce443c9db0ea49d076cad2 to your computer and use it in GitHub Desktop.
Save dsantuc/aec254b82cce443c9db0ea49d076cad2 to your computer and use it in GitHub Desktop.
A more intuitive implementation of PHP's array_merge_recursive()
<?php
function ds_array_merge_recursive () {
$args = func_get_args();
$numArgs = count($args);
if ($numArgs < 1) {
trigger_error(__FUNCTION__ . "() expects at least one parameter, $numArgs given", E_USER_WARNING);
return null;
}
foreach ($args as $i=>$arg) {
if (!is_array($arg)) {
$type = gettype($arg);
$argNum = $i+1;
trigger_error(__FUNCTION__ . "(): Expected parameter $argNum to be an array, $type given", E_USER_WARNING);
return null;
}
}
if ($numArgs < 2) {
return $args[0];
}
if ($numArgs > 2) {
$dest = array_shift($args);
return ds_array_merge_recursive($dest, call_user_func_array('ds_array_merge_recursive', $args));
}
$dest = array_shift($args);
$src = array_shift($args);
foreach ($src as $key=>$value) {
if (is_int($key)) {
$dest[] = $value;
}
elseif (array_key_exists($key, $dest) && is_array($dest[$key]) && is_array($value)) {
$dest[$key] = ds_array_merge_recursive($dest[$key], $value);
}
else {
$dest[$key] = $value;
}
}
return $dest;
}
@dsantuc
Copy link
Author

dsantuc commented Mar 6, 2020

When merging data into an array, PHP's array_merge() overwrites values with the same string key from right to left. Most of the time, this is fine, but if the value sharing the same key happens to be an array, array_merge() overwrites the whole array. There is a function called array_merge_recursive() which merges sub arrays recursively, but it does so in a strange way: instead of overwriting the destination array's value, it creates an array with the values from both arrays.

For example, consider the following arrays:

$a1 = ['foo' => ['a' => 1, 'b' => 1, 'c' => 1]];
$a2 = ['foo' => ['c' => 2, 'd' => 2, 'e' => 2]];
$a3 = ['foo' => ['e' => 3, 'f' => 3, 'g' => 3]];

Each has a sub-array with the key 'foo', each of which in turn has some keys that are unique, but some that are shared.

Merging these arrays with array_merge() produces this result:

php > print_r(array_merge($a1, $a2, $a3));
Array
(
    [foo] => Array
        (
            [e] => 3
            [f] => 3
            [g] => 3
        )

)

The 'foo' sub array from $a3 overwrites those from the first two arrays.

Merging with array_merge_recursive() does this:

php > print_r(array_merge_recursive($a1, $a2, $a3));
Array
(
    [foo] => Array
        (
            [a] => 1
            [b] => 1
            [c] => Array
                (
                    [0] => 1
                    [1] => 2
                )

            [d] => 2
            [e] => Array
                (
                    [0] => 2
                    [1] => 3
                )

            [f] => 3
            [g] => 3
        )

)

$foo['a'] and $foo['b'] are unique to $a1, so those values are preserved. $foo['c'] is shared between $a1 and $a2, so we get an array with both values. $foo['d'] is unique to $a2, so that stays as is, but $foo['e'] is shared between $a2 and $a3, so again we get an array, and finally $foo['f'] and $foo['g'] are unique to $a3, so those values are preserved.

I humbly put it to you, dear reader, that this is not what most people want or expect. I would have expected the values with unique keys to be preserved, but those with shared keys to be overwritten from right to left, as in array_merge().

ds_array_merge_recursive() aims to implement this behavior:

php > print_r(ds_array_merge_recursive($a1, $a2, $a3));
Array
(
    [foo] => Array
        (
            [a] => 1
            [b] => 1
            [c] => 2
            [d] => 2
            [e] => 3
            [f] => 3
            [g] => 3
        )

)

It can accept an arbitrary number of arrays and returns null when called with no arguments or with non-array arguments, emitting a warning (just like array_merge() and array_merge_recursive()), but I think it does more of the Right Thing™ (or at least the expected thing) with respect to duplicated string keys.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment