I'm trying to create a list of categories with any number of sub categories, where sub categories can also has their own sub categories.
I have selected all categories from the Mysql db, the cats are in a standard associate array list, each category has an id, name, parentid where the parentid is 0 if it's top level.
I basically want to be able to take the single level array of cats and turn it into a multidimensional array structure where each category can have an element which will contain an array of subcats.
Now, I can easily achieve this by looping a query for each category but this is far from ideal, I'm trying to do it without any extra hits on the db.
I understand I need a recursive function for this. Can anyone point me in the right direction for this tree style structure?
Cheers
This does the job:
$items = array(
(object) array('id' => 42, 'parent_id' => 1),
(object) array('id' => 43, 'parent_id' => 42),
(object) array('id' => 1, 'parent_id' => 0),
);
$childs = array();
foreach($items as $item)
$childs[$item->parent_id][] = $item;
foreach($items as $item) if (isset($childs[$item->id]))
$item->childs = $childs[$item->id];
$tree = $childs[0];
print_r($tree);
This works by first indexing categories by parent_id. Then for each category, we just have to set category->childs
to childs[category->id]
, and the tree is built !
So, now $tree
is the categories tree. It contains an array of items with parent_id=0, which themselves contain an array of their childs, which themselves ...
Output of print_r($tree)
:
stdClass Object
(
[id] => 1
[parent_id] => 0
[childs] => Array
(
[0] => stdClass Object
(
[id] => 42
[parent_id] => 1
[childs] => Array
(
[0] => stdClass Object
(
[id] => 43
[parent_id] => 42
)
)
)
)
)
So here is the final function:
function buildTree($items) {
$childs = array();
foreach($items as $item)
$childs[$item->parent_id][] = $item;
foreach($items as $item) if (isset($childs[$item->id]))
$item->childs = $childs[$item->id];
return $childs[0];
}
$tree = buildTree($items);
$items = array(
array('id' => 42, 'parent_id' => 1),
array('id' => 43, 'parent_id' => 42),
array('id' => 1, 'parent_id' => 0),
);
$childs = array();
foreach($items as &$item) $childs[$item['parent_id']][] = &$item;
unset($item);
foreach($items as &$item) if (isset($childs[$item['id']]))
$item['childs'] = $childs[$item['id']];
unset($item);
$tree = $childs[0];
So the array version of the final function:
function buildTree($items) {
$childs = array();
foreach($items as &$item) $childs[(int)$item['parent_id']][] = &$item;
foreach($items as &$item) if (isset($childs[$item['id']]))
$item['childs'] = $childs[$item['id']];
return $childs[0]; // Root only.
}
$tree = buildTree($items);
You can fetch all categories at once.
Suppose you have a flat result from the database, like this:
$categories = array(
array('id' => 1, 'parent' => 0, 'name' => 'Category A'),
array('id' => 2, 'parent' => 0, 'name' => 'Category B'),
array('id' => 3, 'parent' => 0, 'name' => 'Category C'),
array('id' => 4, 'parent' => 0, 'name' => 'Category D'),
array('id' => 5, 'parent' => 0, 'name' => 'Category E'),
array('id' => 6, 'parent' => 2, 'name' => 'Subcategory F'),
array('id' => 7, 'parent' => 2, 'name' => 'Subcategory G'),
array('id' => 8, 'parent' => 3, 'name' => 'Subcategory H'),
array('id' => 9, 'parent' => 4, 'name' => 'Subcategory I'),
array('id' => 10, 'parent' => 9, 'name' => 'Subcategory J'),
);
You can create a simple function that turns that flat list into a structure, preferably inside a function. I use pass-by-reference so that there are only one array per category and not multiple copies of the array for one category.
function categoriesToTree(&$categories) {
A map is used to lookup categories quickly. Here, I also created a dummy array for the "root" level.
$map = array(
0 => array('subcategories' => array())
);
I added another field, subcategories, to each category array, and add it to the map.
foreach ($categories as &$category) {
$category['subcategories'] = array();
$map[$category['id']] = &$category;
}
Looping through each categories again, adding itself to its parent's subcategory list. The reference is important here, otherwise the categories already added will not be updated when there are more subcategories.
foreach ($categories as &$category) {
$map[$category['parent']]['subcategories'][] = &$category;
}
Finally, return the subcategories of that dummy category which refer to all top level categories._
return $map[0]['subcategories'];
}
Usage:
$tree = categoriesToTree($categories);
And here is the code in action on Codepad.
See the method :
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['id']] = $element;
}
}
return $branch;
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With