Wayne Haffenden (gravatar)

PHP Deep Object Cloning

I spent many hours yesterday trying to create an exact deep clone of a complex object structure, so that i could make changes to the clone without any affect to the original. I started by using the PHP 5 Object Clone functionality, which creates a shallow copy of all the object's properties, however any properties that are references to other variables, will remain references. This is great, but i needed a deep copy of the object including all references to child objects, however deeply nested. The PHP manual states that when cloning an object, the magic __clone() method is called if possible, which can be used to manually create a deep clone of the object.

Using the magic clone method was a step forwards, i could traverse through the object's properties and any that were references i could create clones of, however this meant that any of the object's properties that were object's that had references were still not cloned. I started implementing an interface and abstract base class, that when derived could clone an object's properties and any that also derived from the same abstract base class could also be cloned, creating a recursive cloning process. This worked perfectly until i hit object properties that were arrays of objects, at which point i decided it was time to take a step back and look for an alternative approach.

It then hit me that if i use the PHP Serialisation functionality to create a byte-stream string representation of the entire object structure and then de-serialise the byte-stream back into the object structure again, that the entire new object would be re-created and therefore be a clone of the original. It worked like a dream, so i've encapsulated it into a static class for re-usablility and have made it open-source. Hopefully the following code will save someone the time that it took me to finally achieve simple deep cloning of an object however complex its structure.

ObjectCloner.php

<?php
final class ObjectCloner
{
private function __construct() {}
public static function cloneObject($source)
{
if ($source === null)
{
return null;
}
return unserialize(serialize($source));
}
}
?>

You can use the above class as in the example class below.

Example.php

<?php
class Example
{
public function someMethod()
{
// We create an instance of some complex object.
$original = new ComplexObject();
// We create an exact clone of the complex object, using the ObjectCloner class.
$clone = ObjectCloner::cloneObject($original);
// Changing some deeply nested property on the clone will have no affect on the original.
$clone->someObjectProperty->someOtherObject->someString = 'NewValue';
echo $original->someObjectProperty->someOtherObject->someString;
// Would produce 'OriginalValue'
echo $clone->someObjectProperty->someOtherObject->someString;
// Would produce 'NewValue'
}
}
?>

I'd appreciate any feedback or comments.

10 Comments

  • Great point, but it has some failures: it wont't work if you have recursive or parent references, because it will isolate the new object from its referenced parents. Helpfull for objects and childs.

  • Wayne&#32;Haffenden (gravatar)

    Wayne said
    September 03, 2010

    That's a good point, however it was only designed as a basic object cloner for parents and children. If you have any suggestions on improving the code to make it an "ultimate" object cloner then I'd love to hear it. Wayne

  • OK I confess that seeing this gave me one of those "slap my forehead and laugh out loud" moments. Of COURSE. How OBVIOUS.
    So many hard ways to do something; and then I see someone do it the easy way and feel both incredibly grateful, and somewhat foolish for not coming up with it myself.
    Thank you!

  • Wayne&#32;Haffenden (gravatar)

    Wayne said
    September 19, 2010

    Thanks for the kind feedback Mike, we all have those moments and can easily get carried away with some complex solution when there is a more simple obvious answer awaiting to be discovered! I'm really glad my code was of some use and saved you the time it took me to work out the same conclusion. Wayne

  • Steve (gravatar)

    Steve said
    October 15, 2010

    Thanks man. This has been driving me nuts all day.

  • Newbie (gravatar)

    Newbie said
    November 02, 2010

    Simplicity at its finest, and works like a charm. Thanks for discovering this and sharing!

  • Awesome post. Worked like a charm for me. Linked via my blog @ http://melikedev.com/2011/01/13/php-deep-clone-an-object/

  • Wayne&#32;Haffenden (gravatar)

    Wayne said
    January 15, 2011

    I'm glad it's helped you all out and thanks for the kind feedback. Mike thanks for linking back to me through an excellent blog post, i encourage any readers to check it out.

  • David&#32;Cloutman (gravatar)

    David Cloutman said
    October 01, 2011

    Saw this solution in PHP manual for the __clone() method and wanted to research it further to see what people had to say about it. Obviously, it is a good quick fix with limitations. I am curious as to how resource intensive it is to call the serialize / unserialize functions to do this. It seems fine if your code isn't constantly using this feature, but I tend to think of serialization as being expensive. Perhaps I am wrong about this, and if so, I'd like someone to set me straight.
    What I find baffling about your implementation is why you need to wrap it in an object. This is something that would work quite well as a stand-alone function. It just seems like a lot of overhead that you don't need in PHP. If this were Java, it would be a different case, but fortunately we don't live in the Kingdom of the Nouns.

  • Wayne&#32;Haffenden (gravatar)

    Wayne said
    October 03, 2011

    Hi David,
    I must admit that when i came up with this solution, I'd only used it in just the one place as a quick fix and therefore hadn't really taken performance into much consideration. I read that the json_encode and json_decode functions are faster than serialize and unserialize and would still achieve the same effect, however, they don't support invoking the __sleep() and __wakeup() methods, so i guess it's dependent on the situation.
    I decided to wrap it in a static class, just so that if i ever decided to change the cloning implementation that calling code wouldn't be affected. Maybe a slight overhead for PHP but as i tend to mainly do .NET development, it feels more natural to me, plus as i use a custom autoloader (spl_autoload_register()) it was only loaded on demand.
    Hope this helps.

Commenting is disabled for this post.