diff --git a/src/Config.php b/src/Config.php index 756319c..a36341f 100644 --- a/src/Config.php +++ b/src/Config.php @@ -22,7 +22,9 @@ use Countable, Iterator, - ArrayAccess; + ArrayAccess, + Zend\Stdlib\ArrayUtils +; /** * @category Zend @@ -335,26 +337,34 @@ public function offsetUnset($offset) /** * Merge another Config with this one. * - * The items in $merge will override the same named items in the current - * config. + * For duplicate keys, the following will be performed: + * - Nested Configs will be recursively merged. + * - Items in $merge with INTEGER keys will be appended. + * - Items in $merge with STRING keys will overwrite current values. * - * @param self $merge - * @return self + * @param Config $replace + * @return Config */ public function merge(self $merge) { - foreach ($merge as $key => $item) { + foreach ($merge as $key => $value) { if (array_key_exists($key, $this->data)) { - if ($item instanceof self && $this->data[$key] instanceof self) { - $this->data[$key] = $this->data[$key]->merge(new self($item->toArray(), $this->allowModifications)); + if (is_int($key)) { + $this->data[] = $value; + } elseif ($value instanceof self && $this->data[$key] instanceof self) { + $this->data[$key]->merge($value); } else { - $this->data[$key] = $item; + if ($value instanceof self) { + $this->data[$key] = new self($value->toArray(), $this->allowModifications); + } else { + $this->data[$key] = $value; + } } } else { - if ($item instanceof self) { - $this->data[$key] = new self($item->toArray(), $this->allowModifications); + if ($value instanceof self) { + $this->data[$key] = new self($value->toArray(), $this->allowModifications); } else { - $this->data[$key] = $item; + $this->data[$key] = $value; } } } diff --git a/src/Factory.php b/src/Factory.php index 3f51100..5fd1366 100644 --- a/src/Factory.php +++ b/src/Factory.php @@ -20,6 +20,8 @@ namespace Zend\Config; +use Zend\Stdlib\ArrayUtils; + /** * Declared abstract to prevent instantiation * @@ -95,7 +97,7 @@ public static function fromFiles(array $files, $returnConfigObject = false) $config = array(); foreach ($files as $file) { - $config = array_replace_recursive($config, self::fromFile($file)); + $config = ArrayUtils::merge($config, self::fromFile($file)); } return ($returnConfigObject) ? new Config($config) : $config; diff --git a/src/Writer/AbstractWriter.php b/src/Writer/AbstractWriter.php index 39ec779..4ae0022 100644 --- a/src/Writer/AbstractWriter.php +++ b/src/Writer/AbstractWriter.php @@ -23,7 +23,7 @@ use Zend\Config\Writer, Zend\Config\Exception, Zend\Config\Config, - Zend\Stdlib\IteratorToArray, + Zend\Stdlib\ArrayUtils, Traversable; /** @@ -77,7 +77,7 @@ function($error, $message = '', $file = '', $line = 0) use ($filename) { public function toString($config) { if ($config instanceof Traversable) { - $config = IteratorToArray::convert($config); + $config = ArrayUtils::iteratorToArray($config); } elseif (!is_array($config)) { throw new Exception\InvalidArgumentException(__METHOD__ . ' expects an array or Traversable config'); } diff --git a/test/ConfigTest.php b/test/ConfigTest.php index 1c1c249..c67efc2 100644 --- a/test/ConfigTest.php +++ b/test/ConfigTest.php @@ -77,6 +77,52 @@ public function setUp() ) ); + $this->toCombineA = array( + 'foo' => 1, + 'bar' => 2, + 'text' => 'foo', + 'numerical' => array( + 'first', + 'second', + array( + 'third' + ) + ), + 'misaligned' => array( + 2 => 'foo', + 3 => 'bar' + ), + 'mixed' => array( + 'foo' => 'bar' + ), + 'replaceAssoc' => array( + 'foo' => 'bar' + ), + 'replaceNumerical' => array( + 'foo' + ) + ); + + $this->toCombineB = array( + 'foo' => 3, + 'text' => 'bar', + 'numerical' => array( + 'fourth', + 'fifth', + array( + 'sixth' + ) + ), + 'misaligned' => array( + 3 => 'baz' + ), + 'mixed' => array( + false + ), + 'replaceAssoc' => null, + 'replaceNumerical' => true + ); + $this->leadingdot = array('.test' => 'dot-test'); $this->invalidkey = array(' ' => 'test', ''=>'test2'); @@ -287,36 +333,55 @@ public function testUnset() public function testMerge() { - $stdArray = array( - 'test_feature' => false, - 'some_files' => array( - 'foo'=>'dir/foo.xml', - 'bar'=>'dir/bar.xml', - ), - 2 => 123, - ); - $stdConfig = new Config($stdArray, true); - - $devArray = array( - 'test_feature'=>true, - 'some_files' => array( - 'bar' => 'myDir/bar.xml', - 'baz' => 'myDir/baz.xml', - ), - 2 => 456, - ); - $devConfig = new Config($devArray); - - $stdConfig->merge($devConfig); - - $this->assertTrue($stdConfig->test_feature); - $this->assertEquals('myDir/bar.xml', $stdConfig->some_files->bar); - $this->assertEquals('myDir/baz.xml', $stdConfig->some_files->baz); - $this->assertEquals('dir/foo.xml', $stdConfig->some_files->foo); - $this->assertEquals(456, $stdConfig->{2}); + $configA = new Config($this->toCombineA); + $configB = new Config($this->toCombineB); + $configA->merge($configB); + + // config-> + $this->assertEquals(3, $configA->foo); + $this->assertEquals(2, $configA->bar); + $this->assertEquals('bar', $configA->text); + + // config->numerical-> ... + $this->assertInstanceOf('\Zend\Config\Config',$configA->numerical); + $this->assertEquals('first',$configA->numerical->{0}); + $this->assertEquals('second',$configA->numerical->{1}); + + // config->numerical->{2}-> ... + $this->assertInstanceOf('\Zend\Config\Config',$configA->numerical->{2}); + $this->assertEquals('third',$configA->numerical->{2}->{0}); + $this->assertEquals(null,$configA->numerical->{2}->{1}); + + // config->numerical-> ... + $this->assertEquals('fourth',$configA->numerical->{3}); + $this->assertEquals('fifth',$configA->numerical->{4}); + + // config->numerical->{5} + $this->assertInstanceOf('\Zend\Config\Config',$configA->numerical->{5}); + $this->assertEquals('sixth',$configA->numerical->{5}->{0}); + $this->assertEquals(null,$configA->numerical->{5}->{1}); + + // config->misaligned + $this->assertInstanceOf('\Zend\Config\Config',$configA->misaligned); + $this->assertEquals('foo',$configA->misaligned->{2}); + $this->assertEquals('bar',$configA->misaligned->{3}); + $this->assertEquals('baz',$configA->misaligned->{4}); + $this->assertEquals(null,$configA->misaligned->{0}); + + // config->mixed + $this->assertInstanceOf('\Zend\Config\Config',$configA->mixed); + $this->assertEquals('bar',$configA->mixed->foo); + $this->assertSame(false,$configA->mixed->{0}); + $this->assertSame(null,$configA->mixed->{1}); + + // config->replaceAssoc + $this->assertSame(null,$configA->replaceAssoc); + + // config->replaceNumerical + $this->assertSame(true,$configA->replaceNumerical); } - + public function testArrayAccess() { $config = new Config($this->all, true); @@ -525,5 +590,39 @@ public function testZF6995_toArrayDoesNotDisturbInternalIterator() $config->toArray(); $this->assertEquals(1, $config->current()); } + + /** + * @depends testMerge + * @link http://framework.zend.com/issues/browse/ZF2-186 + */ + public function testZF2_186_mergeReplacingUnnamedConfigSettings(){ + $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 + ); + + $mergeResult = array( + 'flag' => false, + 'text' => 'bar', + 'list' => array( 'a', 'b', 'c', 'd', 'e' ), + 'aSpecific' => 12, + 'bSpecific' => 100 + ); + + $configA = new Config($arrayA); + $configB = new Config($arrayB); + + $configA->merge($configB); // merge B onto A + $this->assertEquals($mergeResult, $configA->toArray()); + } }