Клонируйте родительско-дочернее дерево в PHP, начните с child и избегайте бесконечной рекурсии - PullRequest
4 голосов
/ 08 октября 2011

У меня есть объектно-ориентированное родительско-дочернее дерево в PHP, которое я хочу клонировать. Сложность заключается в том, что доступ к дереву осуществляется не всегда через корень, а иногда через дочерний элемент корня, например:

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

Итак, я хочу клонировать все дерево, вызвав $new = clone $element1;

Метод __clone () указывает, что каждый из дочерних элементов также должен быть клонирован, и, если проиллюстрирована ситуация *, также должен быть клонирован родительский элемент.

* Root явно установлен как родительский элемент в Element1, поэтому система может идентифицировать эту ситуацию и что-то с ней сделать.

Проблема в том, что, начиная операцию clone с Элемента1, Root также должен быть клонирован. Процедура клонирования для Root предписывает, что все дочерние элементы должны быть клонированы, и поэтому снова вызывается операция clone для Element1, которая затем повторяет ту же процедуру клонирования, создавая бесконечный цикл.

Кроме того, Корень не будет содержать первый клон Element1, но он создаст свой собственный клон, который будет добавлен как ребенок. Элемент 1 будет иметь Root в качестве родителя, но Root не будет иметь тот же элемент 1, что и дочерний элемент.

Надеюсь, я ясно изложил проблему и что кто-то может помочь мне найти решение.

EDIT:

Окончательное решение:

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

Ответы [ 2 ]

1 голос
/ 20 октября 2011

Прежде всего, упростите ваш пример:

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

Тогда различайтесь между тем, что вы делаете, я думаю, у вас есть три операций

  • public метод клонирования.
  • Операция самостоятельного клонирования, которая возвращает клон с дочерними элементами, но не родительским.
  • Операция с одним клоном, которая возвращает клон безchildren.

Код за пределами ваших классов использует публичный метод клонирования.Но метод __clone() не должен использовать этот метод, иначе вы столкнетесь с проблемой циклического зацикливания, которую вы описали.Таким образом, реализация __clone() должна использовать другие методы.

Добавить методы cloneSelf и cloneSingle в ваш класс, сделать их защищенными, чтобы унаследованные классы могли вызывать их, но онине общедоступно.

Затем используйте их в реализации __clone():

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);
}

Эти методы также помогут вам клонировать в случае отсутствия родителя.

1 голос
/ 08 октября 2011

Что если вы добавите параметр к методу __clone()? - давайте назовем это $called_from

На основании значения этого параметра вы знаете, что делать:

  • когда параметр имеет значение по умолчанию 'external' или значение child, тогда клон был вызван из внешнего места, поэтому вы будете вызывать __clone() для parent with, отправив 'child' в качестве значения
  • , когда __clone() наконец вызывается на корневом узле со значением 'child' или 'external', равным $called_from, он запускает реальный процесс клонирования, вызывая __clone() с $called_from, установленным в 'parent' * 1016. *

Редактировать

Мне не было известно о встроенном ключевом слове clone. Таким образом, вы можете создать базовый класс, от которого наследуются все ваши объекты дерева - у этого класса может быть переменная static, которая будет указывать, что клон должен вести себя как

  • при значении true будет выполняться реальный алгоритм clone, в противном случае __clone() будет родительский объект
  • значение по умолчанию false, и только корневой узел устанавливает это значение в true, непосредственно перед тем, как начать клонирование дочерних элементов

Этот базовый класс также может переопределять метод __clone () для реализации этого алгоритма в одном месте.

...