Multi Loop and/or Recursion are simple but performance wise not the best solution.
they are totally fine for smaller datasets but these scale not very well.
a better solution is when every item is only processed one-time.
The Basic Idea is that you create Objects and use the byReference nature of the objects. For that I create "ShadowObjects" that are "glue" everything together, these "ShadowObjects" are later filled with data.
Here an Example
you have a generic id
, parentId
list
$list = [
['id' => 1, 'parent' => 0, 'otherData' => 'a'],
['id' => 2, 'parent' => 1, 'otherData' => 'b'],
['id' => 3, 'parent' => 1, 'otherData' => 'c'],
['id' => 4, 'parent' => 3, 'otherData' => 'd'],
['id' => 5, 'parent' => 3, 'otherData' => 'e'],
['id' => 6, 'parent' => 4, 'otherData' => 'f'],
['id' => 7, 'parent' => 1, 'otherData' => 'g'],
];
The Tree Class
We need an Element that hold and index our Tree-Elements
I like to use an TreeClass for that.
That class Hold all TreeItems in a list with the unique-ID as identifier, the unique-ID is important to get quick access to every Item.
I also like the getRootItems() method to get all Root_Tree_Elements
class Tree
{
private array $items = [];
public function getItem(int $id): ?TreeItem
{
return $this->items[$id]??null;
}
public function setItem(TreeItem $item, int $id): void
{
$this->items[$id] = $item;
}
public function getRootItems(): array
{
$rootItems = [];
foreach ($this->items as $item) {
if ($item->getParent() === null)
$rootItems[] = $item;
}
return $rootItems;
}
}
The Tree Item
is an object that represent a "tree element" it holds the actual data and the parent / children relations
class TreeItem
{
private ?TreeItem $parent = null;
private array $children = [];
private array $data = [];
public function setParent(?TreeItem $parent): void
{
$this->parent = $parent;
}
public function addChild(TreeItem $child): void
{
$this->children[] = $child;
}
public function setData(array $data): void
{
$this->data = $data;
}
}
Processing Loop
And here is The single Loop that creates the TreeStructure:
from the List we have the information that a parent exists with parent_ID X
and that a the item with the ID Y
is a child from it.
So the first thing is we ask our Tree if there is an TreeItem with the id X is already existing (only if the ID is not 0 or NULL or something 'invalid'). If not found we create this Tree Item NEW and add this to the Tree, even we don't know all data for the parent, what we know is only the ID of the parent but that is enough as a 'SHADOW_ITEM'.
after that, we look up for the actual TreeItem. We have all Information for that element. We look it up cuz there is the chance that we create it already as a "ShadowItem", So we can fill it with actual DATA. Keep in mind that TreeItem and the Parent are the same type of Objects.
Here again, if the Item not exists create it new and add the new Item to the TreeList. also Add the ParentTreeItem to the current TreeItem.
Then we fill up our Parent Element with the new Children (Bi-Directional Relation parent knows children / child knows Parent).
We don't need to worry about adding the wrong child to the parent or even duplicate, cuz we only process every item only ONE time.
at the end we fill up the actual data to every item. So that in total every ShadowItem contains the real data and loose the state of a ShadowItem.
$tree = new Tree();
foreach($list as $itemData) {
$id = $itemData['id'];
$pid = $itemData['parent'];
// if ZERO we have root element no parent exists
$parent = $tree->getItem($pid);
if ($pid > 0 && $parent === null) {
$parent = new TreeItem();
$tree->setItem($parent, $pid);
}
// create new tree item if not exists / otherwise get existing item
$item = $tree->getItem($id);
if ($item === null) {
$item = new TreeItem();
$item->setParent($parent);
$tree->setItem($item, $id);
}
if ($parent !== null) {
$parent->addChild($item);
}
$item->setData($itemData);
}
This is an example and "NOT optimize" to make it more easy to understand. there also many things missing on the Tree and the TreeItem to make actual usable,
Feel free to add all methods you like.
for example:
- remove all invalid remaining shadow elements (this can happen if the tree structure data is corrupt, an item points to parent that did not exist anymore)
- or a TreeItem method that give you a ordered array structure of this data and all Children (an __toArray() like).
I use this kind of logic often in Menu generations, it's also easy to "cache"/"store" the finish Tree somewhere. a simple serialize / unserialize do the trick :)