Clone a parent child tree in PHP, start with a child and avoid infinite recursion

I have an object oriented parent child of a PHP tree that I want to clone. The hard part is that access to the tree is not always through the root, but sometimes through a child of the root, for example:

[Root] -- [Element1] START CLONE -- [Element3] -- [Element4] -- [Element2] -- [Element5] 

So what I want to do is clone the whole tree by calling $new = clone $element1;

The __clone () method indicates that each of the children must also be cloned, and if the situation * is illustrated, the parent must also be cloned.

* Root is explicitly set as the parent in element 1, so the system can identify this situation and do something with it.

The problem is that, starting with the clone operation from Element1, Root must also be cloned. The clone procedure for Root requires all children to be cloned, and so the clone operation for Element1 is called again, which then repeats the same clone procedure, creating an endless loop.

In addition, Root will not contain the first clone of Element1, but it will create its own clone to be added as a child of. Element1 will have Root as the parent, but Root will not have the same Element1 as the child.

I hope I have posed the problem explicitly and that someone can help me find a solution.

EDIT:

Final decision:

 /** * The $replace and $with arguments allow a custom cloning procedure. Instead of * being cloned, the original child $replace will be replaced by $with. */ public function duplicate($replace = null, $with = null) { // Basic cloning $clone = clone $this; // If parent is set if(isset($this->parent)) { // Clone parent, replace this element by its clone $parentClone = $this->parent->duplicate($this, $clone); $clone->parent = $parentClone; } // Remove all children in the clone $clone->clear(); // Add cloned children from original to clone foreach($this->getChildren() as $child) { if($child === $replace) // If cloning was initiated from this child, replace with given clone $childClone = $with; else // Else duplicate child normally $childClone = $child->duplicate(); // Add cloned child to this clone $clone->add($childClone); } return $clone; } 
+4
source share
2 answers

First of all, simplify your example:

 [Root] -- [Element1] START CLONE -- [Element3] 

Then differentiate between what you do, I think you have three operations

  • Open cloning method.
  • A self-clone operation that returns a clone with children, but not the parent.
  • A single clone operation that returns a clone without children.

Code outside of your classes uses the public clone method. But the __clone() method should not use this method, otherwise you will encounter the circular loop problem that you described. Thus, the implementation of __clone() should use other methods.

Add the cloneSelf and cloneSingle to your class, make them protected, so inherited classes can call them, but they are not available.

Then use them in your __clone() implementation:

 public function clone() { // clone the parent $parent = $this->getParent(); $parentClone = $parent->cloneSingle(); // clone all children of parent which includes $this $selfClone = NULL; foreach($parent->getChildren() as $child) { $childClone = $child->cloneSelf(); if (!$selfClone && $child === $this) $selfClone = $childClone; $parentClone->addChild($childClone); } assert('$selfClone'); return $selfClone; } public function __clone() { $message = 'Clone operator is not allowed, use clone() method instead.'; throw new BadMethodCallException($message); } 

These methods will also help you clone if there is no parent.

+1
source

What if you add a parameter to the __clone() method? - name it $called_from

Based on the value of this parameter, you know what to do:

  • when the parameter has a default value of β€œexternal” or has a value of child , then the clone is called from an external location, so you call __clone() on the parent, sending β€œchild” as the value
  • when __clone() finally called in the root of the node with the "child" or "external" value set to $called_from , it starts the real cloning process by calling __clone() with $called_from set to 'parent'

Edit

I did not know about the clone built-in key. Thus, you can create a base class that all your tree objects inherit from: this class can have a static variable that will indicate which clone should act as

  • if set to true, the real clone algorithm will be executed, otherwise there will be __clone() parent object
  • the default value is false , and only the root node sets this value to true, just before it starts cloning children

This base class can also override the __clone () method to implement this algorithm in one place.

+1
source

All Articles