Рекурсивная функция для генерации многомерного массива из результата базы данных

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

Например: я извлекаю все страницы в одном запросе, и это выглядит так, как выглядит таблица базы данных

+-------+---------------+---------------------------+ | id | parent_id | title | +-------+---------------+---------------------------+ | 1 | 0 | Parent Page | | 2 | 1 | Sub Page | | 3 | 2 | Sub Sub Page | | 4 | 0 | Another Parent Page | +-------+---------------+---------------------------+ 

И это массив, который я хотел бы обработать в своих файлах:

 Array ( [0] => Array ( [id] => 1 [parent_id] => 0 [title] => Parent Page [children] => Array ( [0] => Array ( [id] => 2 [parent_id] => 1 [title] => Sub Page [children] => Array ( [0] => Array ( [id] => 3 [parent_id] => 1 [title] => Sub Sub Page ) ) ) ) ) [1] => Array ( [id] => 4 [parent_id] => 0 [title] => Another Parent Page ) ) 

Я посмотрел и попробовал почти каждое решение, с которым я столкнулся (их много здесь, в Stack Overflow, но не повезло получить что-то общее, которое будет работать как для страниц, так и для категорий.

Вот ближайший я получил, но он не работает, потому что я назначаю детям родитель первого уровня.

 function page_walk($array, $parent_id = FALSE) { $organized_pages = array(); $children = array(); foreach($array as $index => $page) { if ( $page['parent_id'] == 0) // No, just spit it out and you're done { $organized_pages[$index] = $page; } else // If it does, { $organized_pages[$parent_id]['children'][$page['id']] = $this->page_walk($page, $parent_id); } } return $organized_pages; } function page_list($array) { $fakepages = array(); $fakepages[0] = array('id' => 1, 'parent_id' => 0, 'title' => 'Parent Page'); $fakepages[1] = array('id' => 2, 'parent_id' => 1, 'title' => 'Sub Page'); $fakepages[2] = array('id' => 3, 'parent_id' => 2, 'title' => 'Sub Sub Page'); $fakepages[3] = array('id' => 4, 'parent_id' => 3, 'title' => 'Another Parent Page'); $pages = $this->page_walk($fakepages, 0); print_r($pages); } 

Некоторые очень простые, общие деревья:

 function buildTree(array $elements, $parentId = 0) { $branch = array(); foreach ($elements as $element) { if ($element['parent_id'] == $parentId) { $children = buildTree($elements, $element['id']); if ($children) { $element['children'] = $children; } $branch[] = $element; } } return $branch; } $tree = buildTree($rows); 

Алгоритм довольно прост:

  1. Возьмите массив всех элементов и идентификатор текущего родителя (изначально 0 / nothing / null / whatever).
  2. Прокрутите все элементы.
  3. Если parent_id элемента совпадает с текущим родительским идентификатором, который вы получили в 1., этот элемент является дочерним по отношению к родительскому элементу. Поместите его в список текущих детей (здесь: $branch ).
  4. Вызовите функцию рекурсивно с идентификатором элемента, который вы только что идентифицировали в 3., т. Е. Найдите все дочерние элементы этого элемента и добавьте их как children элемент.
  5. Верните список найденных детей.

Другими словами, одно выполнение этой функции возвращает список элементов, которые являются дочерними элементами данного родительского идентификатора. Вызовите его с помощью buildTree($myArray, 1) , он вернет список элементов, имеющих родительский идентификатор 1. Первоначально эта функция вызывается с родительским идентификатором, равным 0, поэтому возвращаются элементы без родительского идентификатора, которые являются корневыми узлами. Функция вызывает себя рекурсивно, чтобы найти детей детей.

Я знаю, что этот вопрос старый, но я столкнулся с очень похожими проблемами – за исключением очень большого количества данных. После некоторой борьбы мне удалось построить дерево за один проход из набора результатов – используя ссылки. Этот код не очень хорош, но он работает, и он работает довольно быстро. Это нерекурсивный – то есть есть только один проход по набору результатов, а затем один array_filter в конце:

 $dbh = new PDO(CONNECT_STRING, USERNAME, PASSWORD); $dbs = $dbh->query("SELECT n_id, n_parent_id from test_table order by n_parent_id, n_id"); $elems = array(); while(($row = $dbs->fetch(PDO::FETCH_ASSOC)) !== FALSE) { $row['children'] = array(); $vn = "row" . $row['n_id']; ${$vn} = $row; if(!is_null($row['n_parent_id'])) { $vp = "parent" . $row['n_parent_id']; if(isset($data[$row['n_parent_id']])) { ${$vp} = $data[$row['n_parent_id']]; } else { ${$vp} = array('n_id' => $row['n_parent_id'], 'n_parent_id' => null, 'children' => array()); $data[$row['n_parent_id']] = &${$vp}; } ${$vp}['children'][] = &${$vn}; $data[$row['n_parent_id']] = ${$vp}; } $data[$row['n_id']] = &${$vn}; } $dbs->closeCursor(); $result = array_filter($data, function($elem) { return is_null($elem['n_parent_id']); }); print_r($result); 

При выполнении по этим данным:

 mysql> select * from test_table; +------+-------------+ | n_id | n_parent_id | +------+-------------+ | 1 | NULL | | 2 | NULL | | 3 | 1 | | 4 | 1 | | 5 | 2 | | 6 | 2 | | 7 | 5 | | 8 | 5 | +------+-------------+ 

Последний print_r производит этот вывод:

 Array ( [1] => Array ( [n_id] => 1 [n_parent_id] => [children] => Array ( [3] => Array ( [n_id] => 3 [n_parent_id] => 1 [children] => Array ( ) ) [4] => Array ( [n_id] => 4 [n_parent_id] => 1 [children] => Array ( ) ) ) ) [2] => Array ( [n_id] => 2 [n_parent_id] => [children] => Array ( [5] => Array ( [n_id] => 5 [n_parent_id] => 2 [children] => Array ( [7] => Array ( [n_id] => 7 [n_parent_id] => 5 [children] => Array ( ) ) [8] => Array ( [n_id] => 8 [n_parent_id] => 5 [children] => Array ( ) ) ) ) [6] => Array ( [n_id] => 6 [n_parent_id] => 2 [children] => Array ( ) ) ) ) ) 

Это именно то, что я искал.

Можно использовать php, чтобы получить результат mysql в массив, а затем использовать его.

 $categoryArr = Array(); while($categoryRow = mysql_fetch_array($category_query_result)){ $categoryArr[] = array('parentid'=>$categoryRow['parent_id'], 'id'=>$categoryRow['id']); } 

Для больших массивов:

передать массив по ссылке.

 function buildTree(&$elements, $parentId = 0) { //pass main array by reference $branch = array(); foreach ($elements as $key => $element) { if ($element['parent_id'] == $parentId) { $element['children'] = buildTree($elements, $element['id']); $branch[] = $element; } } return $branch; } $tree = buildTree($rows); 

Если вы передадите массив в качестве ссылки, то тот же массив будет использовать в каждой рекурсивной функции, нет необходимости объединять родительские дочерние массивы в конце