ZF2-186: Config::merge() replacing numeric config settings

Description

h3. The problem In a scenario, where a config value is a numerical list (numerical array), {{Config::merge()}} overwrites individual items in the source list instead of appending to it.

h3. Real-world scenario {{Config}} is probably most often used to configure components and modules. Numeric keys are rarely used, but if present, usually describe a list of possible items.

An example for that could be a {{Navigation}} container. A {{Navigation}} container is configured by an ordered list (i.e. numerical array) describing different pages. Let's assume that ModuleA provides a list of pages, and ModuleB provides its own list of pages.

When merging two Config objects from two different modules, the items (pages) from ModuleB will override (replace pages) from ModuleA, which is not an expected behavior.

h3. Sample script



$arrayA = array(
    'flag' => true,
    'text' => 'foo',
    'list' => array( 'a', 'b', 'c' ),
    'aSpecific' => 12
);

$arrayB = array(
    'flag' => false,
    'text' => 'bar',
    'list' => array( 'd', 'e' ),
    'bSpecific' => 100
);

$configA = new Config($arrayA);
$configB = new Config($arrayB);

$configA->merge($configB); // merge B onto A

$expectedMergeResult = array(
    'flag' => false,
    'text' => 'bar',
    'list' => array( 'a', 'b', 'c', 'd', 'e' ),
    'aSpecific' => 12,
    'bSpecific' => 100
);


h3. How does it work outside of ZF? In pure PHP implementation, the primary difference between {{array_merge_recursive()}} and {{array_replace_recursive()}} is that _merge will append (extend) nested, numerical arrays. The other function, _replace, will override numerical values. {{Config::merge()}} method does perform similar operation to {{array_replace_recursive()}} but does not provide any means to select a behavior for handling lists.

h3. The solution The default {{Config::merge()}} behavior needs to be compatible with {{array_merge_recursive()}} - i.e. it has to recognize lists and append their values. For cases when one needs to have a {{replace}} behavior, a separate function will be provided - {{Config::replace()}}

h3. Differences between Config::merge() and array_merge_recursive()

The most important difference between those functions is how they handle string values.

Let's assume we have following arrays:


$arrayA = array( 'foo' => 1, 'bar' => 'text' );
$arrayB = array( 'bar' => 'other text' );

When given duplicate keys and string values {{array_merge_recursive()}} will create an array of values:


print_r(array_merge_recursive($arrayA,$arrayB));
// Array
//(
//    [foo] => 1
//    [bar] => Array
//        (
//            [0] => text
//            [1] => other text
//        )
//
//)

With duplicate keys {{Config::merge()}} will overwrite the value, regardless of its type :


$configA = new Config($arrayA);
$configB = new Config($arrayB);
$configA->merge($configB);

print_r($configA->toArray());
// Array
//(
//    [foo] => 1
//    [bar] => other text
//)

Comments