2008年8月26日星期二

[fw-core] Deep Cloning Zend_Config

I am getting unexpected behaviour when cloning a Zend_Config object:

Example (actual behaviour):

$parent = new Zend_Config(array('key' => array('nested' => 'parent')), true); //allow read-write for merging
$newConfig = clone $parent;
$newConfig->merge(new Zend_Config(array('key' => array('nested' => 'override')), true));
echo $newConfig->key->nested; // 'override' - as expected
echo $parent->key->nested; // 'override' - I was expecting this to be 'parent'


Intuitive behaviour (what makes sense):

Cloning a Zend_Config object should completely separate the new instance
from the original - i.e. there should be no cross-references.

$parent = new Zend_Config(array('key' => array('nested' => 'parent')), true); //allow read-write for merging
$newConfig = clone $parent;
$newConfig->merge(new Zend_Config(array('key' => array('nested' => 'override')), true));
echo $newConfig->key->nested; // 'override'
echo $parent->key->nested; // 'parent'


This is occurring because cloning Zend_Config only creates a shallow
clone currently.

Solution 1: Cast to an array and create a new instance:

This can be achieved already and effectively creates a deep clone.

$parent = new Zend_Config(array('key' => array('nested' => 'parent')), true); //allow read-write for merging
$newConfig = new Zend_Config($parent->toArray(), true); //cast the parent object to an array and create a new Zend_Config
$newConfig->merge(new Zend_Config(array('key' => array('nested' => 'override')), true));
echo $newConfig->key->nested; // 'override' - as expected
echo $parent->key->nested; // 'parent' - as expected

Solution 2: Fixing Zend_Config to perform a deep clone:

/**
* Perform a deep clone of this instance to allow side-effect free cloning.
* @return void
*/
public function __clone()
{
$data = array();
foreach ($this->_data as $key => $value)
{
if ($value instanceof Zend_Config)
{
$data[$key] = clone $value;
} else {
$data[$key] = $value;
}
}
$this->_data = $data;
}

Gives:

$parent = new Zend_Config(array('key' => array('nested' => 'parent')), true); //allow read-write for merging
$newConfig = clone $parent;
$newConfig->merge(new Zend_Config(array('key' => array('nested' => 'override')), true));
echo $newConfig->key->nested; // 'override' - as expected
echo $parent->key->nested; // 'parent' - as expected

It makes sense to me that this is the expected behaviour when cloning
and a deep clone greatly reduces the chance of hard to detect
side-effects. Does anybody disagree?

A full description of the above can be found at:
http://www.daniel-skinner.co.uk/cloning-zend_config-without-side-effects/26/08/2008

Test cases and a patch can be found at:
http://www.destiny-denied.co.uk/files/ZendConfigClone.zip

Kind Regards,

*Daniel Skinner*
**
skinner@destiny-denied.co.uk <mailto:skinner@destiny-denied.co.uk>
www.destiny-denied.co.uk <http://www.destiny-denied.co.uk/>

没有评论: