Хорошо, я действительно застрял в этом. Я надеюсь, что вы можете мне помочь.
У меня есть мой класс, используемый для управления иерархическими данными. Вход представляет собой простой массив со следующей структурой (просто пример):
$list = array( (object) array('id' => 1, 'nombre' => 'Cámaras de fotos', 'parentId' => null), (object) array('id' => 2, 'nombre' => 'Lentes', 'parentId' => null), (object) array('id' => 3, 'nombre' => 'Zoom', 'parentId' => 2), (object) array('id' => 4, 'nombre' => 'SLR', 'parentId' => 1), (object) array('id' => 5, 'nombre' => 'Primarios', 'parentId' => 2), (object) array('id' => 6, 'nombre' => 'Sensor APS-C', 'parentId' => 4), (object) array('id' => 7, 'nombre' => 'Full-frame', 'parentId' => 4), (object) array('id' => 8, 'nombre' => 'Flashes', 'parentId' => null), (object) array('id' => 9, 'nombre' => 'Compactas', 'parentId' => 1) );
Я ввожу данные в класс следующим образом:
$Hierarchical = new Hierarchical; $Hierarchical->plain = $list;
Затем я createTree
публичную функцию ( createTree
) для создания многомерного представления массива списка. Он работает отлично. Он может вернуть результат или сохранить его в $this->tree
.
Как вы можете видеть, это очень просто. Он вызывает частную функцию iterateTree
, которая является рекурсивной функцией.
class Hierarchical { public $plain = array(); public $tree = array(); public function createTree($parentId=0, $return=false) { $tree = $this->iterateTree($parentId); if(!$return) { $this->tree = $tree; } else { return $tree; } } private function iterateTree($parentId) { $resArray = array(); foreach($this->plain as $item) { if($item->parentId == $parentId) { $children = $this->iterateTree($item->id); if( count($children) > 0 ) { $item->children = $children; } $resArray[] = $item; } } return $resArray; } }
Все идет нормально. Он работает нормально.
НО … Проблема возникает, когда я хочу использовать $this->plain
после вызова createTree()
. Вместо того, чтобы возвращать исходный набор данных, он возвращает какой-то микс между исходным входом, причем все их дочерние элементы добавлены (аналогично $this->tree
).
Я не могу понять, почему изменяется содержимое $this->plain
, ни в обеих используемых функциях я не изменяю его содержимое.
Я попытался пропустить переменные внутри foreach
, после foreach
, даже передав исходный массив в качестве аргумента и не используя $this->plain
вообще внутри рекурсивной функции. Ничего не получилось.
Я также не использую какую-либо другую функцию внутри класса, которая могла бы изменить ее значение.
Это полная мистерия!
В вашем цикле foreach
$item
будет ссылкой на объект в массиве, поэтому вы меняете тот же объект в строке
$item->children = $children;
Это повлияет на объект, указанный в исходном массиве $list
и $this->plain
.
Одним из решений может быть клонирование $item
в вашем цикле foreach.
Согласно ответу Дуга, правильная функция: (добавлено $itemAux = clone $item
)
private function iterateTree($parentId) { $resArray = array(); foreach($this->plain as $item) { $itemAux = clone $item; if($itemAux->parentId == $parentId) { $children = $this->iterateTree($itemAux->id); if( count($children) > 0 ) { $itemAux->children = $children; } $resArray[] = $itemAux; } } return $resArray; }
Чтобы добавить к ответу Дуга, хотя в руководстве говорится, что «объекты не передаются по ссылке» (http://www.php.net/manual/en/language.oop5.references.php), он может вместо этого помочь подумать объектов как полностью отдельный объект из любых переменных, которые могут «содержать» их, и что они фактически передаются везде по ссылке …
class testClass { public $var1 = 1; } function testFunc($obj) { $obj->var1 = 2; } $t = new testClass; testFunc($t); echo $t->var1; // 2
Поэтому, когда вы делаете $item->children = $children;
, вы фактически затрагиваете каждый оригинальный объект в этом $plain
массиве.