I have an array of folders, each have a unique ID and a parent ID. What I'm trying to do is find all of the children folders from a parent, even if a child is a sub-sub-folder, or a sub-sub-sub-folder (infinitely). All I need is an array of all of these ID's.
For example if I have the following array
$folders = [
[
'id' => 1,
'parent' => null
],
[
'id' => 2,
'parent' => null
],
[
'id' => 3,
'parent' => 1
],
[
'id' => 4,
'parent' => 3
],
[
'id' => 5,
'parent' => 4
],
[
'id' => 6,
'parent' => 1
]
];
If I want to get all children of folder ID 1, need to be able to loop through and get the following in response:
$children = [3,4,5,6];
I've tried the following:
public function getChildrenIds($folders, $parent_id)
{
$folderIds = [];
foreach($folders as $folder) {
if($folder['parent'] == $parent_id) {
$folderIds[] = $folder['id'];
}
}
return $folderIds;
}
But my question is, how can I make this recusrive?
Rather than make it recursive, you can expand the check to check for all of the so far found parent nodes(using in_array(). This makes it a one pass check..
public function getChildrenIds($folders, $parent_id)
{
// Prime array with parent looking for
$folderIds = [$parent_id];
foreach($folders as $folder) {
// Check if matches any parent so far found
if( in_array($folder['parent'], $folderIds) ) {
$folderIds[] = $folder['id'];
}
}
// Remove parent id added at start
array_shift($folderIds);
return $folderIds;
}
This version does use recursion, it first arranges the array as just the id as the key and the id as the value (using array_column()).
Then as it matches each parent to the id it adds it to a list and then calls itself to add any sub children...
public function getChildrenIds($hierarchy, $parent_id)
{
$folderIds = [];
foreach ( $hierarchy as $id => $folder ) {
if ( $folder == $parent_id ) {
$folderIds[] = $id;
$folderIds = array_merge($folderIds, getChildrenIds($hierarchy, $id));
}
}
return $folderIds;
}
Forgot to add that the array folders should be converted using...
$hierarchy = array_column($folders, 'parent', 'id');
to be passed in.
Related
Need suggestion on the best way to do a traversal on my hierarchy array (at this point I think it's a tree)
A snippet of my array is this:
$rows = array(
array(
'name' => "Main",
'id' => 1,
'parent_id' => 0,
'_children' => array(
array(
'name' => "Two",
'id' => 2,
'parent_id' => 1),
),
array(
'name' => "Three",
'id' => 3,
'parent_id' => 1,
'_children' => array(
array(
'name' => "Four",
'id' => 4,
'parent_id' => 3),
)),
)
)
);
So on that snippet, a quick explanation is that 'Main' node is root and it has 2 children "Two" and "Three" then "Three" has a child namely "Four".
The actual data is based on department and sub-departments so the nodes goes up to 5 layers.
The _children field for my layering is because I use Tabulator and that's the required hierarchy on what I want to achieve.
I was able to achieve using recursion the department hierarchy, now I need to traverse each department so I can add employees for each department on the same field "_children".
The reason I wasn't able to achieve adding the employees from the start, it's because when I do recursion it overwrites the employee on _children with the departments.
Any suggestion on how I should tackle the traversal?
Edit -
Here is my method that I used for hierarchy:
private function buildHierarchyDepartment(array $elements, $parentId = 0) {
$branch = array();
foreach ($elements as $element) {
if ($element['parent_id'] == $parentId) {
$children = static::buildHierarchyDepartment($elements, $element['id']);
if ($children) {
$element['_children'] = $children;
}
$branch[] = $element;
}
}
return $branch;
}
I'm not too sure how you want to add employees to the array so I've made some assumptions here.
This code will traverse through all elements of an array recursively until it finds an element that matches the parent ID. At this point, it will add the specified item to the _children property of that "parent".
NOTE: this can be simplified if you preferred passing the array by reference. For this example I've set it up so that it doesn't edit the original array (unless of course you overwrite the variable).
function addChild(array $main, array $item, $parent_id) {
foreach ($main as $key => $element) {
if ($parent_id === $element["id"]) {
// create _children element if not exist
if (!isset($element["_children"])) {
$element["_children"] = [];
}
$element["_children"][] = $item;
// specify $main[$key] here so that the changes stick
// outside this foreach loop
$main[$key] = $element;
// item added - break the loop
break;
}
// continue to check others if they have children
if (isset($element["_children"])) {
$element["_children"] = addChild($element["_children"], $item, $parent_id);
// specify $main[$key] here so that the changes stick
// outside this foreach loop
$main[$key] = $element;
}
}
return $main;
}
$employee = [
"id" => 99,
"name" => "Test Employee",
"parent_id" => 4,
];
$new_rows = addChild($rows, $employee, $employee["parent_id"]);
NOTE: this uses a strict comparison for $parent_id === $element["id"] meaning an int won't match a string. You can either convert these values into the same format or change to a non-strict compare ==.
suppose I have table named categories such as:
id parent_id title
1 0 food
2 1 drinks
3 2 juice
4 0 furniture
5 3 tables
now I want to create dropdown menu on laravel such that it recursively displays child category under parent category with proper indentation or - mark as per depth.E.g.:
<select>
<option value="1">food</option>
<option value="2">-drinks</option>
<option value="3">--juice</option>
<option value="4">furniture</option>
<option value="5">-tables</option>
</select>
Above one is static but I want to generate dropdown structure dynamically as like above recursively for any depth of child category from categories table in laravel.
First of all, you could define a getCategories method on your controller. A recursive method. Ideally, you should implement something like this:
...
// utility method to build the categories tree
private function getCategories($parentId = 0)
{
$categories = [];
foreach(Category::where('parent_id', 0)->get() as $category)
{
$categories = [
'item' => $category,
'children' => $this->getCategories($category->id)
];
}
return $categories;
}
...
Right after, you should pass the final array/collection (or whatever you choose) to the view.
return view('my_view', ['categories' => $this->getCategories()])
Finally, you could use a solution similar to this one.
Not the most elegant, but gets the job done:
<?php
$data = [
['id' => 1, 'parent_id' => 0, 'title' => 'food'],
['id' => 2, 'parent_id' => 1, 'title' => 'drinks'],
['id' => 3, 'parent_id' => 2, 'title' => 'juice'],
['id' => 4, 'parent_id' => 0, 'title' => 'furniture'],
['id' => 5, 'parent_id' => 4, 'title' => 'tables']
];
function recursiveElements($data) {
$elements = [];
$tree = [];
foreach ($data as &$element) {
$element['children'] = [];
$id = $element['id'];
$parent_id = $element['parent_id'];
$elements[$id] =& $element;
if (isset($elements[$parent_id])) { $elements[$parent_id]['children'][] =& $element; }
else { $tree[] =& $element; }
}
return $tree;
}
function flattenDown($data, $index=0) {
$elements = [];
foreach($data as $element) {
$elements[] = str_repeat('-', $index) . $element['title'];
if(!empty($element['children'])) $elements = array_merge($elements, flattenDown($element['children'], $index+1));
}
return $elements;
}
$recursiveArray = recursiveElements($data);
$flatten = flattenDown($recursiveArray);
print_r($flatten);
/*
Outputs:
Array
(
[0] => food
[1] => -drinks
[2] => --juice
[3] => furniture
[4] => -tables
)
*/
Run get method on your Category Eloquent model or use query builder to get all the categories.
Then write a function and call it recursively as many times as you need. filter method would be really helpful to work with your categories collection
Something like this should work:
function getCategories($categories, &$result, $parent_id = 0, $depth = 0)
{
//filter only categories under current "parent"
$cats = $categories->filter(function ($item) use ($parent_id) {
return $item->parent_id == $parent_id;
});
//loop through them
foreach ($cats as $cat)
{
//add category. Don't forget the dashes in front. Use ID as index
$result[$cat->id] = str_repeat('-', $depth) . $cat->title;
//go deeper - let's look for "children" of current category
getCategories($categories, $result, $cat->id, $depth + 1);
}
}
//get categories data. In this case it's eloquent.
$categories = Category::get();
//if you don't have the eloquent model you can use DB query builder:
//$categories = DB::table('categories')->select('id', 'parent_id', 'title')->get();
//prepare an empty array for $id => $formattedVal storing
$result = [];
//start by root categories
getCategories($categories, $result);
Didn't test it myself, but the idea should be clear enough. The good thing is you're only executing a single query. The bad thing is you load the whole table into memory at once.
If your table has more columns that you don't need for this algorithm you should specify only the needed ones in your query.
I use PHP and mySQL with Idiorm. That might not be relevant.
My PHP array
It's a relationship between parents and childs.
0 is the root parent.
Example: Root parent 0 have the child 33 which have the child 27 which have
the child 71.
This array structure can be changed if needed for solving the problem.
array (
33 =>
array (
0 => '27',
1 => '41',
),
27 =>
array (
0 => '64',
1 => '71',
),
0 =>
array (
0 => '28',
1 => '29',
2 => '33',
),
)
My hierarchical result
Something like this, but as an array...
0 =>
28
29
33
27 =>
64
71
41
Information
The depth are unkown and it can be unlimited. I tried foreach, but it might not be the way.
My own thoughts
Some recursive function?
Some while loops?
I tried both of the above, just got a mess. It's a brainer.
The suggestion by #deceze worked. However the input array needs to change a litte, like this...
$rows = array(
array(
'id' => 33,
'parent_id' => 0,
),
array(
'id' => 34,
'parent_id' => 0,
),
array(
'id' => 27,
'parent_id' => 33,
),
array(
'id' => 17,
'parent_id' => 27,
),
);
From https://stackoverflow.com/a/8587437/476:
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);
print_r( $tree );
I added to #Jens Törnell's answers to enable defining the options for the column name of parent_id, the children array key name, and also the column name for id.
/**
* function buildTree
* #param array $elements
* #param array $options['parent_id_column_name', 'children_key_name', 'id_column_name']
* #param int $parentId
* #return array
*/
function buildTree(array $elements, $options = [
'parent_id_column_name' => 'parent_id',
'children_key_name' => 'children',
'id_column_name' => 'id'], $parentId = 0)
{
$branch = array();
foreach ($elements as $element) {
if ($element[$options['parent_id_column_name']] == $parentId) {
$children = buildTree($elements, $options, $element[$options['id_column_name']]);
if ($children) {
$element[$options['children_key_name']] = $children;
}
$branch[] = $element;
}
}
return $branch;
}
Since the functionality is quite universal, I managed to use the above function in most of my projects.
great answer from #Jens Törnell, just wanted to add a little improvement that if your parent_id and id is actually string instead of number then above method will fail and after creating children array, it will create those childrens arrays again as separate individual array. In order to fix that you should do triple equal check and by telling data type of variable i.e (string) in comparison.
For string based Id and Parent_id in array
function buildTree(array $elements, $parentId = 0) {
$branch = array();
foreach ($elements as $element) {
if ((string)$element['parent_id'] === (string)$parentId) {
$children = buildTree($elements, $element['id']);
if ($children) {
$element['children'] = $children;
}
$branch[] = $element;
}
}
return $branch;
}
additionally if someone desire, he can add a third parameter to function as well to specify data type of variables dynamically i.e function buildTree(array $elements, $parentId = 0, $datatype='string') but then you will have to take of any other error occur.
hope it will help someone!
public function createTree (&$list, $parentId = null) {
$tree = array();
foreach ($list as $key => $eachNode) {
if ($eachNode['parentId'] == $parentId) {
$eachNode['children'] = $this->createTree ($list,$eachNode['id']);
$tree[] = $eachNode;
unset($list[$key]);
}
}
return $tree;
}
In that function pass the associative array and if the most parent is not null then just pass the most parent id as second argument.
I had a different problem and could not find a solution that worked for me on this page. I needed to create a tree but without knowing the root id.
This means I have to go through my flat array and build branches with the most parently items at the top of the tree.
If anyone else needs to build a tree without a root parent item id, here's how I did it.
<?php
$rows = [
(object) [
'id' => 1001,
'parentid' => 1000,
'name' => 'test1.1'
],
(object) [
'id' => 1000,
'parentid' => 100,
'name' => 'test1'
],
(object) [
'id' => 1002,
'parentid' => 1000,
'name' => 'test1.2'
],
(object) [
'id' => 1004,
'parentid' => 1001,
'name' => 'test1.1.1'
],
(object) [
'id' => 1005,
'parentid' => 1004,
'name' => 'test1.1.1.1'
],
(object) [
'id' => 100,
'parentid' => 10,
'name' => 'test 0'
],
(object) [
'id' => 1006,
'parentid' => 1002,
'name' => 'test1.2.1'
],
(object) [
'id' => 1007,
'parentid' => 1002,
'name' => 'test1.2.2'
],
];
function add_child(stdClass $parent, stdClass $child) {
if ($child->parentid != $parent->id) {
throw new Exception('Attempting to add child to wrong parent');
}
if (empty($parent->children)) {
$parent->children = [];
} else {
// Deal where already in branch.
foreach ($parent->children as $idx => $chd) {
if ($chd->id === $child->id) {
if (empty($chd->children)) {
// Go with $child, since $chd has no children.
$parent->children[$idx] = $child;
return;
} else {
if (empty($child->children)) {
// Already has this child with children.
// Nothing to do.
return;
} else {
// Both childs have children - merge them.
$chd->children += $child->children;
$parent->children[$idx] = $child;
return;
}
}
}
}
}
$parent->children[] = $child;
}
function build_branch(&$branch, &$rows, &$parent = null) {
$hitbottom = false;
while (!$hitbottom) {
$foundsomething = false;
// Pass 1 - find children.
$removals = []; // Indexes of rows to remove after this loop.
foreach ($rows as $idx => $row) {
if ($row->parentid === $branch->id) {
// Found a child.
$foundsomething = true;
// Recurse - find children of this child.
build_branch($row, $rows, $branch);
add_child($branch, $row);
$removals[] = $idx;
}
}
foreach ($removals as $idx) {
unset($rows[$idx]);
}
// Pass 2 - find parents.
if ($parent === null) {
$foundparent = false;
foreach ($rows as $idx => $row) {
if ($row->id === $branch->parentid) {
// Found parent
$foundsomething = true;
$foundparent = true;
add_child($row, $branch);
unset ($rows[$idx]);
// Now the branch needs to become the parent since parent contains branch.
$branch = $row;
// No need to search for other parents of this branch.
break;
}
}
}
$hitbottom = !$foundsomething;
}
}
function build_tree(array $rows) {
$tree = [];
while (!empty($rows)) {
$row = array_shift($rows);
build_branch($row, $rows);
$tree[] = $row;
}
return $tree;
}
$tree = build_tree($rows);
print_r($tree);
I have a data set stored in an array that references itself with parent-child ids:
id, parent_id, title etc. The top tier has a parent_id of 0, and there can be countless parent-child relationships.
So I'm sorting through this array with a foreach loop within a recursive function to check each array element against its parent element, and I think I've been staring at this method too long.
I do end up with the elements in the correct order, but I can't seem to get my lists nested correctly, which makes me think that the method doesn't really work.
Is this the best route to take?
What can I do to improve and fix this method
Is there another trick that I can apply?
Here is my source:
<div>
<div>Subpages</div>
<ul>
<?php subPages($this->subpages->toArray(), 0) ?>
</ul>
<br>
Add New Subpage
</div>
<?php
function subPages($subpages, $parent){
foreach($subpages as $key => &$page){
$newParent = $page['id'];
//If the current page is the parrent start a new list
if($page['id'] == $parent)
{
//Echo out a new list
echo '<ul>';
echo '<li class="collapsed">';
echo '+';
echo ''.$page['title'].'';
subPages($subpages, $newParent);
echo '</li>';
echo '</ul>';
}
//If the page's parent id matches the parent provided
else if($page['parent_id'] == $parent)
{
//Echo out the link
echo '<li class="collapsed">';
echo '+';
echo ''.$page['title'].'';
//Set the page as the new parent
$newParent = $page['id'];
//Remove page from array
unset($subpages[$key]);
//Check the rest of the array for children
subPages($subpages, $newParent);
echo '</li>';
}
}
}
?>
As always, any assistance is appreciated. Please let me know if something isn't clear.
I doubt that you guys are still looking for a real answer to this, but it might help out others with the same problem. Below is a recursive function to resort an array placing children beneath parents.
$initial = array(
array(
'name' => 'People',
'ID' => 2,
'parent' => 0
),
array(
'name' => 'Paul',
'ID' => 4,
'parent' => 2
),
array(
'name' => 'Liz',
'ID' => 5,
'parent' => 2
),
array(
'name' => 'Comus',
'ID' => 6,
'parent' => 3
),
array(
'name' => 'Mai',
'ID' => 7,
'parent' => 2
),
array(
'name' => 'Titus',
'ID' => 8,
'parent' => 3
),
array(
'name' => 'Adult',
'ID' => 9,
'parent' => 6
),
array(
'name' => 'Puppy',
'ID' => 10,
'parent' => 8
),
array(
'name' => 'Programmers',
'ID' => 11,
'parent' => 4
) ,
array(
'name' => 'Animals',
'ID' => 3,
'parent' => 0
)
);
/*---------------------------------
function parentChildSort_r
$idField = The item's ID identifier (required)
$parentField = The item's parent identifier (required)
$els = The array (required)
$parentID = The parent ID for which to sort (internal)
$result = The result set (internal)
$depth = The depth (internal)
----------------------------------*/
function parentChildSort_r($idField, $parentField, $els, $parentID = 0, &$result = array(), &$depth = 0){
foreach ($els as $key => $value):
if ($value[$parentField] == $parentID){
$value['depth'] = $depth;
array_push($result, $value);
unset($els[$key]);
$oldParent = $parentID;
$parentID = $value[$idField];
$depth++;
parentChildSort_r($idField,$parentField, $els, $parentID, $result, $depth);
$parentID = $oldParent;
$depth--;
}
endforeach;
return $result;
}
$result = parentChildSort_r('ID','parent',$initial);
print '<pre>';
print_r($result);
print '</pre>';
It's a wind down method that removes elements from the original array and places them into result set in the proper order. I made it somewhat generic for you, so it just needs you to tell it what your 'ID' field and 'parent' fields are called. Top level items are required to have a parent_id (however you name it) of 0. I also add a depth marker to each item so that you can format on output.
I will try to help you.
It is possible to compose such relations in one pass:
/**
* Used for "recursive" folding of layout items
* Algorithm of infinite tree (non recursive method)
*
* #param array $items
* #return array
*/
function _foldItems($items) {
$result = array();
foreach ($items as $key => $item) {
$itemName = $item['name'];
if (!isset($item['parent']))
continue;
else {
$parentName = $item['parent']; // it can be either `name` or some `id` of the parent item
if (isset($result[$itemName][$item['sequence']])) {
// Done to eliminate `Warning: Cannot use a scalar value as an array in atLeisure_PropertyImport.class.php`
// Sometimes elements already in the list and have [name] => $count and next line tries to put item in array (item becomes parent)
if ( isset($result[$parentName][$item['parentSequence']]['items'][$itemName]) AND
is_scalar($result[$parentName][$item['parentSequence']]['items'][$itemName])
)
$result[$parentName][$item['parentSequence']]['items'][$itemName] = array();
$result[$parentName][$item['parentSequence']]['items'][$itemName][$item['sequence']] = $result[$itemName][$item['sequence']];
unset($result[$itemName][$item['sequence']]);
} else
$result[$parentName][$item['parentSequence']]['items'][$itemName] = $item['count'];
unset($items[$key]);
} // if //
if (empty($result[$itemName]))
unset($result[$itemName]);
} // foreach //
foreach ($items as $item) { // enumerating rest of the items (single items)
$itemName = $item['itemName'];
if (!isset($result[$itemName]))
$result[$itemName][$item['sequence']] = $item['count'];
}
return $result;
}
Example can be a bit hard to read and to understand because there is really too much code, but I've made this function not so long ago for one project and it seems to be work successfully.
NOTE: It will also work if there are several same items linked to one parent item. It uses item sequence number to avoid aliasing similar values into one.
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);
Here is the same version, with arrays, which is a little tricky as we need to play with references (but works equally well):
$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;
}
I had the same problem and solved it this way: fetch cat rows from DB and for each root categories, build tree, starting with level (depth) 0. May not be the most efficient solution, but works for me.
$globalTree = array();
$fp = fopen("/tmp/taxonomy.csv", "w");
// I get categories from command line, but if you want all, you can fetch from table
$categories = $db->fetchCol("SELECT id FROM categories WHERE parentid = '0'");
foreach ($categories as $category) {
buildTree($category, 0);
printTree($category);
$globalTree = array();
}
fclose($file);
function buildTree($categoryId, $level)
{
global $db, $globalTree;
$rootNode = $db->fetchRow("SELECT id, name FROM categories WHERE id=?", $categoryId);
$childNodes = $db->fetchAll("SELECT * FROM categories WHERE parentid = ? AND id <> ? ORDER BY id", array($rootNode['id'], $rootNode['id']));
if(count($childNodes) < 1) {
return 0;
} else {
$childLvl = $level + 1;
foreach ($childNodes as $childNode) {
$id = $childNode['id'];
$childLevel = isset($globalTree[$id])? max($globalTree[$id]['depth'], $level): $level;
$globalTree[$id] = array_merge($childNode, array('depth' => $childLevel));
buildTree($id, $childLvl);
}
}
}
function printTree($categoryId) {
global $globalTree, $fp, $db;
$rootNode = $db->fetchRow("SELECT id, name FROM categories WHERE id=?", $categoryId);
fwrite($fp, $rootNode['id'] . " : " . $rootNode['name'] . "\n");
foreach ($globalTree as $node) {
for ($i=0; $i <= $node['depth']; $i++) {
fwrite($fp, ",");
}
fwrite($fp, $node['id'] " : " . $node['name'] . "\n");
}
}
ps. I am aware that OP is looking for a solution without DB queries, but this one involves recursion and will help anybody who stumbled across this question searching for recursive solution for this type of question and does not mind DB queries.
If the parent key is not passed from the class object then my code will create a root category and if the parent value is passed then child will create under the parent root.
class CategoryTree {
public $categories = array();
public function addCategory(string $category, string $parent=null) : void
{
if( $parent ) {
if ( array_key_exists($parent , $this->categories ) ) {
$this->categories[$parent][] = $category;
}
else {
$this->categories[$parent] = array();
$this->categories[$parent][] = $category;
}
}
else {
if ( ! array_key_exists($category , $this->categories ) ) {
$this->categories[$category] = array();
}
}
}
public function getChildren(string $parent = null) : array
{
$data = [];
if ( array_key_exists($parent , $this->categories ) ) {
$data = $this->categories[$parent];
}
return $data;
}
}
$c = new CategoryTree;
$c->addCategory('A', null);
$c->addCategory('B', 'A');
$c->addCategory('C', 'A');
$c->addCategory('C', 'E');
$c->addCategory('D', 'E');
$c->addCategory('D', null);
$c->addCategory('N', 'D');
$c->addCategory('A', null);
$c->addCategory('G', 'A');
echo implode(',', $c->getChildren('A'));