Search code examples
phpclone

PHP Cloning Children


I have an object that is a somewhat basic tree. I needed to make a deep copy of it and found myself implementing the __clone method. The successful code is:

function __clone() {
    $object = new CustomXML($this->rootElement);
    foreach ($this->elements as $key => $element) {
        $this->elements[$key] = clone $this->elements[$key];
        $object->elements[$key] = $this->elements[$key];
    }
    $object->attributes = $this->attributes;
    $object->value = $this->value;
    $object->allowHTML = $this->allowHTML;
    $object->endTag = $this->endTag;
    $object->styles = $this->styles;
    $object->childID = $this->childID;

    return $object;
}

My question is... Why do I have to use

$this->elements[$key] = clone $this->elements[$key];
$object->elements[$key] = $this->elements[$key];

Why can't I just use

$object->elements[$key] = clone $this->elements[$key];

Using the second one still leaves a reference to the children. Why is this? The values in $this->elements are of the same class.


Solution

  • __clone() is invoked on an already-created shallow copy of an object. See the documentation

    PHP will shallow-copy all properties and create a new object without calling its constructor (similar to serialization and deserialization). Then PHP calls __clone() on the new object so you can modify it according to your whims. Because it modifies the object, it should not return anything.

    Your code (if you always want a deep copy) should look like this:

       function __clone() {
            foreach ($this->children as $key => $child) {
                $this->children[$key] = clone $this->children[$key];
            }
       }
    

    However I strongly recommend that you do not do this! Instead of relying on the clone keyword, add methods to return cloned objects explicitly (e.g., DOMNode::cloneNode(). This will, for example, let you control whether your copies should be shallow or deep. If you just use clone you cannot control this.

    Here is an example:

    interface DeepCopyable
    {
        /**
         * Return a copy of the current object
         *
         * @param $deep bool If TRUE, return a deep copy
         * @return object
         */
        public function copy($deep=false);
    }
    
    class TreeNode implements DeepCopyable
    {
        private $I_AM_A_CLONE = false;
        protected $children = array();
    
        function __clone() {
            $this->I_AM_A_CLONE = true;
        }
    
        public function addChild(Copyable $child) {
            $this->children[] = $child;
        }
    
        public function copy($deep=false) {
            $copy = clone $this;
            if ($deep) {
                foreach ($this->children as $n => $child) {
                    $copy->children[$n] = $child->copy($deep);
                }
            }
            return $copy;
        }
    }
    
    
    $a = new TreeNode();
    $a->addChild(new TreeNode());
    var_dump($a);
    var_dump($a->copy());
    var_dump($a->copy(true));
    

    This example also illustrates the proper use of __clone(). You need to add a clone magic method when a private or protected property of the cloned object should not be identical to the original. For example, if you have an id property on an object which should be unique, you may want to ensure that a clone will not have the same ID and you never ever want calling code to control that. Or a "dirty" flag, or whatever.