sitemap

Cloning Zend_Config without side-effects

August 26th, 2008 by Daniel Skinner

Cloning in PHP5

PHP5 with its much improved object-oriented style passes objects by reference by default. To create a copy of an object, a new construct 'clone' is introduced. Cloning an object in PHP5 creates a shallow copy - any properties that are references to other variables, will remain references. Additionally, PHP5 brings the __clone() magic method to allow you to customise the cloning behaviour of objects, making those referenced objects full copies if that is the desired behaviour.

Zend_Config

Zend_Config is a component of the Zend Framework. Zend_Config makes it trivial to load configuration data into PHP from a range of file types.

Using Zend_Config is easy:

$config = new Zend_Config_Xml('/path/to/config');
echo $config->database->host;
 

In a particular scenario of mine I have a heirachy of config files, each inheriting data from it's parent and overwriting keys if necessary. Merging two config files to get the resultant is also trivial:

(void) $parent->merge($child);
 

The Zend_Config instance $parent has now been augmented with the values of the Zend_Config instance $child and keys may have been overwritten in the parent if a value for that key existed in the child.

Here lies my problem: What I really wanted to do was keep $parent as it was whilst creating a new instance that was the product of $parent merged with $child.

Easy:

 
 
$newConfig = clone $parent;
$newConfig->merge($child);
 

Or not... Currently Zend_Config only implements a shallow clone which effectively means my $newConfig is still tied up with $parent. In fact if I make changes to $newConfig, those changes may well be reflected in $parent. This is not the side-effect-free behaviour I expected when using clone.

It is true that Zend_Config is typically meant to be used in read-only situations but the power of merging has been realised and implemented, making Zend_Config often used in read-write situations aswell (here writing refers to changing the values in the instance, not the source configuration file).

Here is an example of the problem:

 
 
$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'
 

This is actually perfectly reasonable behaviour for a shallow clone - The nested data value was cloned only by reference so (Zend_Config) $newConfig->key is actually a reference to the original (Zend_Config) $parent->key object and so changing any one will affect the other. However, this is not what I would expect to happen when cloning a Zend_Config object.

Solution 1: toArray

The toArray solution is simple: Flatten the Zend_Config object to a simple array and create a brand new Zend_Config object with the data. All objects are copied into the array and we get a deep copy of the object.

$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: Performing a Deep Clone

OK - That's how deep cloning is achieved with the current release but what about the problem of the misleading and unexpected operation of the clone construct? The simple and intuitive solution is to create a __clone() method that generates a full deep clone which is completely independent:

 
 
    /**
     * 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;
    }
 

Now we can do a full clone intuitively without having to cast to an array first:

 
 
$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
 

Summary

To summarise, cloning a Zend_Config object doesn't have intuitive results due to shallow cloning. To get a full clone we must cast the original object to an array before creating a brand new instance. Alternatively, the Zend_Config class should implement deep cloning to get the desired and expected behaviour.

Test cases, the extended class, and a patch can be downloaded here (tests work on R9566 as provided in the file).

Supporting Test Cases

 
 
/**
 * This file is part of ZendConfigClone
 *
 * @package   ZendConfigClone
 * @author    Daniel Skinner
 */
require_once dirname(__FILE__) . '/../AbstractTest.php';
/**
 */
class ZendConfigCloneTest extends AbstractTest
{
    /**
     * 0 - Use original Zend_Config object R9566
     * 1 - Use new ZendConfigClone object
     *
     */
	const VERSION = 1;
    public function setUp ()
    {
        $this->baseConfig = self::factory(array(
            'value1' => 'base1',
            'value2' => array(
                'nestedkey' => 'nestedvalue'
            )
        ), true);
    }
    /**
     * As expected, flattening the object into an array prevents any
     * references from being maintained and allows for a complete clone
     * of the Zend_Config object.
     *
     */
    public function testToArrayDoesNotKeepReferences()
    {
    	$newConfig = self::factory($this->baseConfig->toArray(), true);
    	$newConfig->value1 = 'newvalue1';
 
    	$this->assertNotEquals($newConfig->value1, $this->baseConfig->value1);
    	$this->assertEquals($newConfig->value2->nestedkey, $this->baseConfig->value2->nestedkey);
    }
    /**
     * As expected, flattening the object into an array prevents any
     * references from being maintained and allows for a complete clone
     * of the Zend_Config object.
     *
     */
    public function testNestedToArrayDoesNotKeepReferences()
    {
        $newConfig = self::factory($this->baseConfig->toArray(), true);
        $newConfig->value2->nestedkey = 'newvalue1';
 
        $this->assertNotEquals($newConfig->value2->nestedkey, $this->baseConfig->value2->nestedkey);
    }
    /**
     * With the current Zend_Config, a shallow clone will copy
     * first level values correctly.
     *
     */
    public function testShallowCloneDoesNotKeepReferences()
    {
        $newConfig = clone $this->baseConfig;
        $newConfig->value1 = 'newvalue1';
 
        $this->assertNotEquals($newConfig->value1, $this->baseConfig->value1);
    }
    /**
     * Currently cloning Zend_Config isn't side-effect free.
     *
     * A shallow clone means deep nested Zend_Config objects are
     * not cloned and changing values in the cloned object still
     * affects the original.
     *
     * This test should pass with ZendConfigClone
     *
     */
    public function testDeepCloneDoesNotKeepReferences()
    {
        $newConfig = clone $this->baseConfig;
        $newConfig->value2->nestedkey = 'newvalue1';
 
        $this->assertNotEquals($newConfig->value2->nestedkey, $this->baseConfig->value2->nestedkey);
    }
    /**
     * Factory method for generating a concrete Zend_Config instance
     *
     * @param mixed $arg,... Arguments to pass to the constructor
     * @return Zend_Config
     */
    private static function factory()
    {
    	$args = func_get_args();
    	switch (self::VERSION)
    	{
    		//Zend_Config
    		case 0:
    			$refObj = new ReflectionClass('Zend_Config');
    			return $refObj->newInstanceArgs($args);
    			break;
    		//ZendConfigClone
    		case 1:
                $refObj = new ReflectionClass('ZendConfigClone');
                return $refObj->newInstanceArgs($args);
    			break;
    	}
    	throw new Exception('VERSION should be enum{0, 1}');
    }
}
 

A new class with deep clone functionality for testing:

 
 
/**
 * This file is part of ZendConfigClone
 *
 * @package   ZendConfigClone
 * @author    Daniel Skinner
 */
 
class ZendConfigClone extends Zend_Config
{
    /**
     * 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;
    }
}
 



Install phpUnderControl on CentOS 5

June 12th, 2008 by Daniel Skinner

A while back now I started setting up a server to use as a development machine. From a standard CentOS 5 installation I have installed SubVersion and Trac. Now I have finally got round to installing phpUnderControl for continuous integration and build management.

As usual, there are many great guides already on the net but getting everything to work usually requires a few of these references and a few additional changes. I will point out where I got the information from where applicable but the aim of this guide is to be complete and comprehensive.

If you follow this guide you should start with a basic CentOS 5 installation with Subversion setup correctly and end up with a working installation of phpUnderControl.

Read the rest of this entry »




MooTools and Swiff: Slideshow / Thumbnail Gallery

May 17th, 2008 by Daniel Skinner

I have recently launched a website for the Bramham Lodge Residential Development in Bramham Village.

The site features a neat Slideshow / Thumbnail Gallery implementation using MooTools and Flash (utilising the Swiff utility of MooTools 1.2).

A demo can be seen on the Bramham Lodge Apartment page.

The SlideShow makes use of Flash to create easily customisable image transition effects that are not possible with JavaScript alone. A collection of MooTools classes are employed to mange the Swiff interface and control the slideshow.

Key Features

  • Images are not hard-coded into flash: no need to recompile the SWF when adding/removing or re-ordering the gallery as all data is presented in the XHTML markup.
  • Extensible and customisable: The slideshow class makes use of MooTools Events so that add-on classes can be created for bolt-on features whilst being kept separate from the core functionality.
  • Accessible: No content is hard-coded into flash, search engines are able to index all the information.

The modular nature of the code and the ability to change the implementation using bolt-on functionality has allowed me to re-use the same set of classes on an upcoming project in a completely different fashion, making use of the great features of Flash without sacrificing indexable content and accessibility.




Hair by Caroline

April 14th, 2008 by Emma Place

Hair by Caroline first launched back in 2005...three years down the line and we decided it was in great need of a re-design. It was also important to update the content, adding more material and creating a new gallery was among the important factors to consider.

15thApril 2008 has been re-launched, with a new and improved navigation, modern images and a whole lot more content to help improve its ranking in the search engines.




External CSS won’t load in IE7

April 5th, 2008 by Daniel Skinner

I just spent a while wondering why IE7 wouldn't make use of an external stylesheet when it worked in Firefox no problems.

The page is in UTF-8 and the .css file was also encoded in UTF-8 but IE7 simply refused to render the page using the CSS rules.

I have no idea  what the problem is but if you are having similar issues try putting @charset “UTF-8″; at the top of your external stylesheet - it worked for me.

Does anyone know what the underlying issue is?




Link Targets in XHTML

March 15th, 2008 by Daniel Skinner

I always prefer to build websites in valid XHTML as it allows for a stricter, more maintainable envorinment to build websites than traditional HTML. However, there are a few features of HTML I like to make use of.

For example, making links open in new windows using the target attribute is unsupported by XHTML.

Read the rest of this entry »




Yicrosoft Directory Competition

February 9th, 2008 by Emma Place

Ryan at SEO Noobs have hosted a competition, which many of you are probably aware of by now. The key word is “Yicrosoft Directory” which is a word which had zero results in the Google search when the competition started; it’s also a word that has been put together in the light of Microsoft’s $44.6 billion bid to take over Yahoo. All the competitors have until 4th March 2008 to produce a website to appear at number one in Google.com search engine rankings. I decided to take part in this competition, not only because it is interesting but to also give myself the opportunity to create and optimise the content of a website the best I can. I guess this is really going to test my skills as a writer! If I can aim to post something remotely interesting daily, perhaps I could get the site to page one! Who knows, but as time goes on more and more people are entering the competition, the Google search alone now holds nearly 2,000 results for “Yicrosoft Directory”. Only time will tell! Of course everyone is in it to win but in all honesty if our website makes it to number one in the Google search I will be more than happy. I’ll keep you all posted on the progression.

I decided to create a Yahoo Directory parody website: http://yicrosoftdirectory.com/. At this point I would like to invite everyone and anyone to submit any type of original, written content to help broaden the horizons! Your help would be much appreciated. If you don’t have anything to say why not come along and vote on the poll anyway.




Chaining with MooTools 1.2 - Tutorial

January 31st, 2008 by Daniel Skinner

This guide will show how powerful the MooTools Chain class is. In MooTools chaining facilitates the execution of a stack of functions sequentially and is extremely powerful. I have only tested this in MooTools v1.2 beta 2.

I will be posting more of these short guides on using MooTools in the upcoming weeks. Each will focus on a small problem and solve it with MooTools with the aim of being a useful way to learn the framework.

Read the rest of this entry »




Zend Certified Engineer!

January 31st, 2008 by Daniel Skinner

I finally got round to booking the Zend PHP 5 Exam and successfully passed earlier this week!

I didn't think that the exam was particularly hard but whilst revising I did learn about a few interesting features of the language.

I would recommend becoming a Zend Engineer to anyone who is serious about PHP software development. If anyone is interested I have a few online practice exams going spare.

For more information on Zend certification, see this article

Zend Certified Engineer




Nofollow on WordPress Comments Still?

January 15th, 2008 by Daniel Skinner

I have only just noticed that comments on this WordPress blog have rel="nofollow" set as default. I believe that if someone is willing to contribute to a blog by commenting then the least they deserve is an inlink to their site.

A quick search shows that this is the default for all WordPress installations. The main reason seems to be to dissuade spamming. However, with the new Akismet anti-spam plugin I have never seen one spam comment get through the net. Is it time for WordPress to change the default setting and give commenters at least a bit of recognition?

Nofollow Free

I don't see the reason for nofollow and I am more than willing to give people some recognition for commenting on my blog. Bloggers should encourage quality comments as much as possible. For this reason I have installed the Nofollow Case by Case plugin to remove nofollow from links by default and this blog is now nofollow free.

Is there a reason?

Have I missed the real reason for nofollow on comments? Is it time for WordPress to change the default setting? Are commenters more inclined to comment on a blog that doesn't use nofollow?

Feel free to give your opinions and correct me if I am wrong, there is a little bit on link juice in it for you! :)