Рекурсивная функция для создания многомерного массива из результата базы данных
Я собираюсь написать функцию, которая берет массив страниц/категорий (из результата плоской базы данных) и генерирует массив вложенных элементов страниц/категорий на основе родительских идентификаторов. Я хотел бы сделать это рекурсивно, чтобы можно было выполнить любой уровень вложенности.
Например: я извлекаю все страницы в одном запросе, и вот как выглядит таблица базы данных
+-------+---------------+---------------------------+
| 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);
}
3 answers
Несколько очень простых, общих построений дерева:
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);
Алгоритм довольно прост:
- Возьмите массив всех элементов и идентификатор текущего родителя (изначально
0
/ничего/null
/что угодно). - Цикл по всем элементам.
- Если
parent_id
элемента совпадает с текущим идентификатором родителя, полученным в 1., элемент является дочерним элементом родителя. Внесите его в свой список текущих детей (здесь:$branch
). - Вызовите функцию рекурсивно с идентификатором элемента, который вы только что определили в 3., т. Е. Найдите всех дочерних элементов этого элемента и добавьте их как элемент
children
. - Верните свой список найденных детей.
Другими словами, одно выполнение этой функции возвращает список элементов, которые являются дочерними элементами данного родительского идентификатора. Вызовите его с помощью 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']);
}