Nested lists using PHP's iterator? - php

I'm trying to display this kind of array:
$nodes = array(
1 => array(
'title' => 'NodeLvl1',
'children' => array(),
),
2 => array(
'title' => 'NodeLvl1',
'children' => array(
1 => array(
'title' => 'NodeLvl2',
'children' => array(),
),
2 => array(
'title' => 'NodeLvl2',
'children' => array(
1 => array(
'title' => 'NodeLvl3',
'children' => array(),
),
2 => array(
'title' => 'NodeLvl3',
'children' => array(),
),
),
),
),
),
3 => array(
'title' => 'NodeLvl1',
'children' => array(),
),
);
like this:
<ul>
<li>
NodeLvl1
</li>
<li>
NodeLvl1
<ul>
<li>NodeLv2</li>
...
</ul>
</li>
...
Basically a nested list taking into account the "children" property. So far I've come up with this:
class It extends RecursiveIteratorIterator{
protected
$tab = "\t";
public function beginChildren(){
if(count($this->getInnerIterator()) == 0)
return;
echo str_repeat($this->tab, $this->getDepth())."<ul>\n";
}
public function endChildren(){
if(count($this->getInnerIterator()) == 0)
return;
echo str_repeat($this->tab, $this->getDepth())."\n</ul>";
}
public function nextElement(){
echo str_repeat($this->tab, $this->getDepth() + 1).'<li>';
}
}
$it = new It(new RecursiveArrayIterator($nodes));
foreach($it as $key => $item)
echo $item;
Which doesn't work quite right: I get each item wrapped between <ul>s and I don't know how can I close <li>s...
Any ideas on how to make this work? Also is it possible to get all the array properties (the actual element), instead of just the "title" property inside my foreach() loop? And can this be done with objects instead of arrays?

Do you need a class iterator for this? You could do this with just a simple function...
function arrayToListHTML($array, $level = 0) {
static $tab = "\t";
if (empty($array)) return;
$tabs = str_repeat($tab, $level * 2);
$result = "{$tabs}<ul>\n";
foreach ($array as $i => $node):
$result .= "{$tabs}{$tab}<li>\n{$tabs}{$tab}{$tab}{$node['title']}\n".arrayToListHTML($node['children'], $level + 1)."{$tabs}{$tab}</li>\n";
endforeach;
$result .= "{$tabs}</ul>\n";
return $result;
}
Which will produce this output:
<ul>
<li>
NodeLvl1
</li>
<li>
NodeLvl1
<ul>
<li>
NodeLvl2
</li>
<li>
NodeLvl2
<ul>
<li>
NodeLvl3
</li>
<li>
NodeLvl3
</li>
</ul>
</li>
</ul>
</li>
<li>
NodeLvl1
</li>
</ul>
This covers what you've shown us, but I'm not sure what you mean by other properties. Are there more properties in each array other than title and children?

Instead of trying to use your class like an array in foreach() consider using your class to perform the function. For instance, the following code will output correctly but the function is performed inside the class.
class It extends RecursiveIteratorIterator{
protected
$tab = "\t";
public function beginChildren(){
if(count($this->getInnerIterator()) == 0)
return;
echo str_repeat($this->tab, $this->getDepth())."<ul>\n";
}
public function endChildren(){
if(count($this->getInnerIterator()) == 0)
return;
echo str_repeat($this->tab, $this->getDepth)."\n</ul>";
}
public function nextElement(){
echo str_repeat($this->tab, $this->getDepth())."<li>".$this->current()."</li>\n";
}
}
$it = new It(new RecursiveArrayIterator($nodes));
foreach($it as $key => $item)
//echo $item;
//it will be better to write a function inside your custom iterator class to handle iterations
?>

You can use RecursiveCachingIterator to do what you want. Here is an example, (source: https://github.com/cballou/PHP-SPL-Iterator-Interface-Examples/blob/master/recursive-caching-iterator.php)
<?php
// example navigation array
$nav = array(
'Home' => '/home',
'Fake' => array(
'Double Fake' => array(
'Nested Double Fake' => '/fake/double/nested',
'Doubly Nested Double Fake' => '/fake/double/doubly'
),
'Triple Fake' => '/fake/tripe'
),
'Products' => array(
'Product 1' => '/products/1',
'Product 2' => '/products/2',
'Product 3' => '/products/3',
'Nested Product' => array(
'Nested 1' => '/products/nested/1',
'Nested 2' => '/products/nested/2'
)
),
'Company' => '/company',
'Privacy Policy' => '/privacy-policy'
);
class NavBuilder extends RecursiveIteratorIterator {
// stores the previous depth
private $_depth = 0;
// stores the current iteration's depth
private $_curDepth = 0;
// store the iterator
protected $_it;
/**
* Constructor.
*
* #access public
* #param Traversable $it
* #param int $mode
* #param int $flags
*/
public function __construct(Traversable $it, $mode = RecursiveIteratorIterator::SELF_FIRST, $flags = 0)
{
parent::__construct($it, $mode, $flags);
// store the caching iterator
$this->_it = $it;
}
/**
* Override the return values.
*
* #access public
*/
public function current()
{
// the return output string
$output = '';
// set the current depth
$this->_curDepth = parent::getDepth();
// store the difference in depths
$diff = abs($this->_curDepth - $this->_depth);
// get the name and url of the nav item
$name = parent::key();
$url = parent::current();
// close previous nested levels
if ($this->_curDepth < $this->_depth) {
$output .= str_repeat('</ul></li>', $diff);
}
// check if we have the last nav item
if ($this->hasNext()) {
$output .= '<li>' . $name . '';
} else {
$output .= '<li class="last">' . $name . '';
}
// either add a subnav or close the list item
if ($this->hasChildren()) {
$output .= '<ul>';
} else {
$output .= '</li>';
}
// cache the depth
$this->_depth = $this->_curDepth;
// return the output ( we could've also overridden current())
return $output;
}
}
?>
Usage
<?php
try {
// generate the recursive caching iterator
$it = new RecursiveCachingIterator(new RecursiveArrayIterator($nav));
// build the navigation with the iterator
$it = new NavBuilder($it, RecursiveIteratorIterator::SELF_FIRST);
// display the resulting navigation
echo '<ul id="nav">' . PHP_EOL;
foreach ($it as $value) {
echo $value . "\n";
}
echo '</ul>' . PHP_EOL;
} catch (Exception $e) {
var_dump($e); die;
}
?>

First let me explain few things to you. Your array has two pattens
One with numeric indexes
One with string indexes, with title and children which has be parsed differently
I think a recursive function plays very nice role on this part, rather than complex logics. And our recursive function has to be able to handle both patterns separately.
Here is my version of the function you could use with explanation
function arraytolist(Array $array) { //ensure what you receive is array
if(count($array)) { //only if it has some items
//In case the array has `title` index we encountered out PATTERN 2
if(isset($array['title'])) {
$o = "<li>";
$o .= $array['title']; //simply add the title
$o .= arraytolist($array['children']); //and pass the children to this function to verify again
$o .= "</li>";
} else { //if its a normal array, //PATTERN 1
$o = "<ul>";
foreach($array as $value) {
$n = "";
if(is_array($value)) { //in case its an array again,
//send it to this very same function so that it will return as output again
$n .= arraytolist($value);
} else {
$n .= "<li>$value</li>";
}
$o .= strlen($n) ? $n : ""; //if $n has something use it otherwise not
}
$o .= "</ul>"; //lets close the ul
}
return $o;
}
}
Some Advantage of this function
No iteration level
As long as its an array and has item, keeps on building them
Power of simple logic in PHP

I would opt for a simple recursive function that flattens the array into the text/html format:
function arrToList( $arr, $embedded = false ) {
$output = array();
if ( $embedded ) $output[] = '<li>';
$output[] = '<ul>';
foreach ( $arr as $key => $values ) {
$output[] = '<li>'.$values['title'].'</li>';
if ( $values['children'] ) {
$output[] = arrToList( $values['children'], true );
}
}
$output[] = '</ul>';
if ( $embedded ) $output[] = '</li>';
return implode(PHP_EOL, $output);
}
Output from using your input:
NodeLvl1
NodeLvl1
NodeLvl2
NodeLvl2
NodeLvl3
NodeLvl3
NodeLvl1
or the actual code:
<ul>
<li>NodeLvl1</li>
<li>NodeLvl1</li>
<li>
<ul>
<li>NodeLvl2</li>
<li>NodeLvl2</li>
<li>
<ul>
<li>NodeLvl3</li>
<li>NodeLvl3</li>
</ul>
</li>
</ul>
</li>
<li>NodeLvl1</li>
</ul>
Cheers

Related

May I know if there are examples of nested loop for json below [duplicate]

I have a bunch of name-parentname pairs, that I'd like to turn into as few heirarchical tree structures as possible. So for example, these could be the pairings:
Child : Parent
H : G
F : G
G : D
E : D
A : E
B : C
C : E
D : NULL
Which needs to be transformed into (a) heirarchical tree(s):
D
├── E
│ ├── A
│ │ └── B
│ └── C
└── G
├── F
└── H
The end result that I want is a nested set of <ul> elements, with each <li> containing the child's name.
There are no inconsistencies in the pairings (child is it's own parent, parent is child's child, etc), so a bunch of optimizations can probably be made.
How, in PHP, would I go from an array containing child=>parent pairs, to a set of Nested <ul>s?
I have a feeling that recursion is involved, but I'm not quite awake enough to think it through.
This requires a very basic recursive function to parse the child/parent pairs to a tree structure and another recursive function to print it out. Only one function would suffice but here's two for clarity (a combined function can be found at the end of this answer).
First initialize the array of child/parent pairs:
$tree = array(
'H' => 'G',
'F' => 'G',
'G' => 'D',
'E' => 'D',
'A' => 'E',
'B' => 'C',
'C' => 'E',
'D' => null
);
Then the function that parses that array into a hierarchical tree structure:
function parseTree($tree, $root = null) {
$return = array();
# Traverse the tree and search for direct children of the root
foreach($tree as $child => $parent) {
# A direct child is found
if($parent == $root) {
# Remove item from tree (we don't need to traverse this again)
unset($tree[$child]);
# Append the child into result array and parse its children
$return[] = array(
'name' => $child,
'children' => parseTree($tree, $child)
);
}
}
return empty($return) ? null : $return;
}
And a function that traverses that tree to print out an unordered list:
function printTree($tree) {
if(!is_null($tree) && count($tree) > 0) {
echo '<ul>';
foreach($tree as $node) {
echo '<li>'.$node['name'];
printTree($node['children']);
echo '</li>';
}
echo '</ul>';
}
}
And the actual usage:
$result = parseTree($tree);
printTree($result);
Here's the contents of $result:
Array(
[0] => Array(
[name] => D
[children] => Array(
[0] => Array(
[name] => G
[children] => Array(
[0] => Array(
[name] => H
[children] => NULL
)
[1] => Array(
[name] => F
[children] => NULL
)
)
)
[1] => Array(
[name] => E
[children] => Array(
[0] => Array(
[name] => A
[children] => NULL
)
[1] => Array(
[name] => C
[children] => Array(
[0] => Array(
[name] => B
[children] => NULL
)
)
)
)
)
)
)
)
If you want a bit more efficiency, you can combine those functions into one and reduce the number of iterations made:
function parseAndPrintTree($root, $tree) {
$return = array();
if(!is_null($tree) && count($tree) > 0) {
echo '<ul>';
foreach($tree as $child => $parent) {
if($parent == $root) {
unset($tree[$child]);
echo '<li>'.$child;
parseAndPrintTree($child, $tree);
echo '</li>';
}
}
echo '</ul>';
}
}
You'll only save 8 iterations on a dataset as small as this but on bigger sets it could make a difference.
Yet Another Function To Make A Tree (no recursion involved, uses references instead):
$array = array('H' => 'G', 'F' => 'G', ..., 'D' => null);
function to_tree($array)
{
$flat = array();
$tree = array();
foreach ($array as $child => $parent) {
if (!isset($flat[$child])) {
$flat[$child] = array();
}
if (!empty($parent)) {
$flat[$parent][$child] =& $flat[$child];
} else {
$tree[$child] =& $flat[$child];
}
}
return $tree;
}
Returns an hierarchical array like this one:
Array(
[D] => Array(
[G] => Array(
[H] => Array()
[F] => Array()
)
...
)
)
Which can easily be printed as a HTML list using recursive function.
Another, more simplified way to convert the flat structure in the $tree into a hierarchy. Only one temporary array is needed to expose it:
// add children to parents
$flat = array(); # temporary array
foreach ($tree as $name => $parent)
{
$flat[$name]['name'] = $name; # self
if (NULL === $parent)
{
# no parent, is root element, assign it to $tree
$tree = &$flat[$name];
}
else
{
# has parent, add self as child
$flat[$parent]['children'][] = &$flat[$name];
}
}
unset($flat);
That's all for getting the hierarchy into a multidimensional array:
Array
(
[children] => Array
(
[0] => Array
(
[children] => Array
(
[0] => Array
(
[name] => H
)
[1] => Array
(
[name] => F
)
)
[name] => G
)
[1] => Array
(
[name] => E
[children] => Array
(
[0] => Array
(
[name] => A
)
[1] => Array
(
[children] => Array
(
[0] => Array
(
[name] => B
)
)
[name] => C
)
)
)
)
[name] => D
)
The output is less trivial if you want to avoid recursion (can be a burden with large structures).
I always wanted to solve the UL/LI "dilemma" for outputting an array. The dilemma is, that each item does not know whether or not children will follow up or how many preceding elements need to be closed. In another answer I already solved that by using a RecursiveIteratorIterator and looking for getDepth() and other meta-information that my own written Iterator provided: Getting nested set model into a <ul> but hiding “closed” subtrees. That answer shows as well that with iterators you're quite flexible.
However that was a pre-sorted list, so would not be suitable for your example. Additionally I always wanted to solve this for a sort of standard tree structure and HTML's <ul> and <li> elements.
The basic concept I came up is the following:
TreeNode - Abstracts each element into a simple TreeNode type that can provide it's value (e.g. Name) and whether or not it has children.
TreeNodesIterator - A RecursiveIterator that is able to iterate over a set (array) of these TreeNodes. That is fairly simple as the TreeNode type already knows if it has children and which ones.
RecursiveListIterator - A RecursiveIteratorIterator that has all the events needed when it recursively iterate over any kind of RecursiveIterator:
beginIteration / endIteration - Begin and end of the main list.
beginElement / endElement - Begin and end of each element.
beginChildren / endChildren - Begin and end of each children list.
This RecursiveListIterator only provides these events in a form of function calls. children lists, as it is typical for <ul><li> lists, are opened and closed inside it's parent <li> element. Therefore the endElement event is fired after the according endChildren event. This could be changed or made configurable to broaden the use this class. The events are distributed as function calls to a decorator object then, to keep things apart.
ListDecorator - A "decorator" class that is just a receiver of the events of RecursiveListIterator.
I start with the main output logic. Taken the now hierarchical $tree array, the final code looks like the following:
$root = new TreeNode($tree);
$it = new TreeNodesIterator(array($root));
$rit = new RecursiveListIterator($it);
$decor = new ListDecorator($rit);
$rit->addDecorator($decor);
foreach($rit as $item)
{
$inset = $decor->inset(1);
printf("%s%s\n", $inset, $item->getName());
}
First let's look into the ListDecorator that simply wraps the <ul> and <li> elements and is deciding about how the list structure is output:
class ListDecorator
{
private $iterator;
public function __construct(RecursiveListIterator $iterator)
{
$this->iterator = $iterator;
}
public function inset($add = 0)
{
return str_repeat(' ', $this->iterator->getDepth()*2+$add);
}
The constructor takes the list iterator it's working on. inset is just a helper function for nice indentation of the output. The rest are just the output functions for each event:
public function beginElement()
{
printf("%s<li>\n", $this->inset());
}
public function endElement()
{
printf("%s</li>\n", $this->inset());
}
public function beginChildren()
{
printf("%s<ul>\n", $this->inset(-1));
}
public function endChildren()
{
printf("%s</ul>\n", $this->inset(-1));
}
public function beginIteration()
{
printf("%s<ul>\n", $this->inset());
}
public function endIteration()
{
printf("%s</ul>\n", $this->inset());
}
}
With these output functions in mind, this is the main output wrap-up / loop again, I go through it step by step:
$root = new TreeNode($tree);
Create the root TreeNode which will be used to start iteration upon:
$it = new TreeNodesIterator(array($root));
This TreeNodesIterator is a RecursiveIterator that enables recursive iteration over the single $root node. It is passed as an array because that class needs something to iterate over and allows re-use with a set of children which is also an array of TreeNode elements.
$rit = new RecursiveListIterator($it);
This RecursiveListIterator is a RecursiveIteratorIterator that provides the said events. To make use of it, only a ListDecorator needs to be provided (the class above) and assigned with addDecorator:
$decor = new ListDecorator($rit);
$rit->addDecorator($decor);
Then everything is set-up to just foreach over it and output each node:
foreach($rit as $item)
{
$inset = $decor->inset(1);
printf("%s%s\n", $inset, $item->getName());
}
As this example shows, the whole output logic is encapsulated in the ListDecorator class and this single foreach. The whole recursive traversal has been fully encapsulated into SPL recursive iterators which provided a stacked procedure, that means internally no recursion function calls are done.
The event based ListDecorator allows you to modify the output specifically and to provide multiple type of lists for the same data structure. It's even possible to change the input as the array data has been encapsulated into TreeNode.
The full code example:
<?php
namespace My;
$tree = array('H' => 'G', 'F' => 'G', 'G' => 'D', 'E' => 'D', 'A' => 'E', 'B' => 'C', 'C' => 'E', 'D' => null);
// add children to parents
$flat = array(); # temporary array
foreach ($tree as $name => $parent)
{
$flat[$name]['name'] = $name; # self
if (NULL === $parent)
{
# no parent, is root element, assign it to $tree
$tree = &$flat[$name];
}
else
{
# has parent, add self as child
$flat[$parent]['children'][] = &$flat[$name];
}
}
unset($flat);
class TreeNode
{
protected $data;
public function __construct(array $element)
{
if (!isset($element['name']))
throw new InvalidArgumentException('Element has no name.');
if (isset($element['children']) && !is_array($element['children']))
throw new InvalidArgumentException('Element has invalid children.');
$this->data = $element;
}
public function getName()
{
return $this->data['name'];
}
public function hasChildren()
{
return isset($this->data['children']) && count($this->data['children']);
}
/**
* #return array of child TreeNode elements
*/
public function getChildren()
{
$children = $this->hasChildren() ? $this->data['children'] : array();
$class = get_called_class();
foreach($children as &$element)
{
$element = new $class($element);
}
unset($element);
return $children;
}
}
class TreeNodesIterator implements \RecursiveIterator
{
private $nodes;
public function __construct(array $nodes)
{
$this->nodes = new \ArrayIterator($nodes);
}
public function getInnerIterator()
{
return $this->nodes;
}
public function getChildren()
{
return new TreeNodesIterator($this->nodes->current()->getChildren());
}
public function hasChildren()
{
return $this->nodes->current()->hasChildren();
}
public function rewind()
{
$this->nodes->rewind();
}
public function valid()
{
return $this->nodes->valid();
}
public function current()
{
return $this->nodes->current();
}
public function key()
{
return $this->nodes->key();
}
public function next()
{
return $this->nodes->next();
}
}
class RecursiveListIterator extends \RecursiveIteratorIterator
{
private $elements;
/**
* #var ListDecorator
*/
private $decorator;
public function addDecorator(ListDecorator $decorator)
{
$this->decorator = $decorator;
}
public function __construct($iterator, $mode = \RecursiveIteratorIterator::SELF_FIRST, $flags = 0)
{
parent::__construct($iterator, $mode, $flags);
}
private function event($name)
{
// event debug code: printf("--- %'.-20s --- (Depth: %d, Element: %d)\n", $name, $this->getDepth(), #$this->elements[$this->getDepth()]);
$callback = array($this->decorator, $name);
is_callable($callback) && call_user_func($callback);
}
public function beginElement()
{
$this->event('beginElement');
}
public function beginChildren()
{
$this->event('beginChildren');
}
public function endChildren()
{
$this->testEndElement();
$this->event('endChildren');
}
private function testEndElement($depthOffset = 0)
{
$depth = $this->getDepth() + $depthOffset;
isset($this->elements[$depth]) || $this->elements[$depth] = 0;
$this->elements[$depth] && $this->event('endElement');
}
public function nextElement()
{
$this->testEndElement();
$this->event('{nextElement}');
$this->event('beginElement');
$this->elements[$this->getDepth()] = 1;
}
public function beginIteration()
{
$this->event('beginIteration');
}
public function endIteration()
{
$this->testEndElement();
$this->event('endIteration');
}
}
class ListDecorator
{
private $iterator;
public function __construct(RecursiveListIterator $iterator)
{
$this->iterator = $iterator;
}
public function inset($add = 0)
{
return str_repeat(' ', $this->iterator->getDepth()*2+$add);
}
public function beginElement()
{
printf("%s<li>\n", $this->inset(1));
}
public function endElement()
{
printf("%s</li>\n", $this->inset(1));
}
public function beginChildren()
{
printf("%s<ul>\n", $this->inset());
}
public function endChildren()
{
printf("%s</ul>\n", $this->inset());
}
public function beginIteration()
{
printf("%s<ul>\n", $this->inset());
}
public function endIteration()
{
printf("%s</ul>\n", $this->inset());
}
}
$root = new TreeNode($tree);
$it = new TreeNodesIterator(array($root));
$rit = new RecursiveListIterator($it);
$decor = new ListDecorator($rit);
$rit->addDecorator($decor);
foreach($rit as $item)
{
$inset = $decor->inset(2);
printf("%s%s\n", $inset, $item->getName());
}
Outpupt:
<ul>
<li>
D
<ul>
<li>
G
<ul>
<li>
H
</li>
<li>
F
</li>
</ul>
</li>
<li>
E
<ul>
</li>
<li>
A
</li>
<li>
C
<ul>
<li>
B
</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
</ul>
Demo (PHP 5.2 variant)
A possible variant would be an iterator that iterates over any RecursiveIterator and provides an iteration over all events that can occur. An switch / case inside the foreach loop could then deal with the events.
Related:
Nested List with RecursiveArrayIterator
The RecursiveIteratorIterator class
While Alexander-Konstantinov's solution might not seem as easy to read at first, it is both genius and exponentially better in terms of performance, this should have been voted as the best answer.
Thanks mate, I made a benchmark in your honor to compare the 2 solutions presented in this post.
I had an #250k flat tree with 6 levels that I had to convert and I was searching for a better way to do so and avoid recursive iterations.
Recursion vs Reference:
// Generate a 6 level flat tree
$root = null;
$lvl1 = 13;
$lvl2 = 11;
$lvl3 = 7;
$lvl4 = 5;
$lvl5 = 3;
$lvl6 = 1;
$flatTree = [];
for ($i = 1; $i <= 450000; $i++) {
if ($i % 3 == 0) { $lvl5 = $i; $flatTree[$lvl6] = $lvl5; continue; }
if ($i % 5 == 0) { $lvl4 = $i; $flatTree[$lvl5] = $lvl4; continue; }
if ($i % 7 == 0) { $lvl3 = $i; $flatTree[$lvl3] = $lvl2; continue; }
if ($i % 11 == 0) { $lvl2 = $i; $flatTree[$lvl2] = $lvl1; continue; }
if ($i % 13 == 0) { $lvl1 = $i; $flatTree[$lvl1] = $root; continue; }
$lvl6 = $i;
}
echo 'Array count: ', count($flatTree), PHP_EOL;
// Reference function
function treeByReference($flatTree)
{
$flat = [];
$tree = [];
foreach ($flatTree as $child => $parent) {
if (!isset($flat[$child])) {
$flat[$child] = [];
}
if (!empty($parent)) {
$flat[$parent][$child] =& $flat[$child];
} else {
$tree[$child] =& $flat[$child];
}
}
return $tree;
}
// Recursion function
function treeByRecursion($flatTree, $root = null)
{
$return = [];
foreach($flatTree as $child => $parent) {
if ($parent == $root) {
unset($flatTree[$child]);
$return[$child] = treeByRecursion($flatTree, $child);
}
}
return $return ?: [];
}
// Benchmark reference
$t1 = microtime(true);
$tree = treeByReference($flatTree);
echo 'Reference: ', (microtime(true) - $t1), PHP_EOL;
// Benchmark recursion
$t2 = microtime(true);
$tree = treeByRecursion($flatTree);
echo 'Recursion: ', (microtime(true) - $t2), PHP_EOL;
The output speaks for itself:
Array count: 255493
Reference: 0.3259289264679 (less than 0.4s)
Recursion: 6604.9865279198 (almost 2h)
Well, first I would turn the straight array of key-value pairs into a hierarchical array
function convertToHeiarchical(array $input) {
$parents = array();
$root = array();
$children = array();
foreach ($input as $item) {
$parents[$item['id']] = &$item;
if ($item['parent_id']) {
if (!isset($children[$item['parent_id']])) {
$children[$item['parent_id']] = array();
}
$children[$item['parent_id']][] = &$item;
} else {
$root = $item['id'];
}
}
foreach ($parents as $id => &$item) {
if (isset($children[$id])) {
$item['children'] = $children[$id];
} else {
$item['children'] = array();
}
}
return $parents[$root];
}
That'll can convert a flat array with parent_id and id into a hierarchical one:
$item = array(
'id' => 'A',
'blah' => 'blah',
'children' => array(
array(
'id' => 'B',
'blah' => 'blah',
'children' => array(
array(
'id' => 'C',
'blah' => 'blah',
'children' => array(),
),
),
'id' => 'D',
'blah' => 'blah',
'children' => array(
array(
'id' => 'E',
'blah' => 'blah',
'children' => array(),
),
),
),
),
);
Then, just create a rendering function:
function renderItem($item) {
$out = "Your OUtput For Each Item Here";
$out .= "<ul>";
foreach ($item['children'] as $child) {
$out .= "<li>".renderItem($child)."</li>";
}
$out .= "</ul>";
return $out;
}
Well, to parse into ULs and LIs, it would be something like:
$array = array (
'H' => 'G'
'F' => 'G'
'G' => 'D'
'E' => 'D'
'A' => 'E'
'B' => 'C'
'C' => 'E'
'D' => 'NULL'
);
recurse_uls ($array, 'NULL');
function recurse_uls ($array, $parent)
{
echo '<ul>';
foreach ($array as $c => $p) {
if ($p != $parent) continue;
echo '<li>'.$c.'</li>';
recurse_uls ($array, $c);
}
echo '</ul>';
}
But I'd love to see a solution that doesn't require you to iterate through the array so often...
Here's what I came up with:
$arr = array(
'H' => 'G',
'F' => 'G',
'G' => 'D',
'E' => 'D',
'A' => 'E',
'B' => 'C',
'C' => 'E',
'D' => null );
$nested = parentChild($arr);
print_r($nested);
function parentChild(&$arr, $parent = false) {
if( !$parent) { //initial call
$rootKey = array_search( null, $arr);
return array($rootKey => parentChild($arr, $rootKey));
}else { // recursing through
$keys = array_keys($arr, $parent);
$piece = array();
if($keys) { // found children, so handle them
if( !is_array($keys) ) { // only one child
$piece = parentChild($arr, $keys);
}else{ // multiple children
foreach( $keys as $key ){
$piece[$key] = parentChild($arr, $key);
}
}
}else {
return $parent; //return the main tag (no kids)
}
return $piece; // return the array built via recursion
}
}
outputs:
Array
(
[D] => Array
(
[G] => Array
(
[H] => H
[F] => F
)
[E] => Array
(
[A] => A
[C] => Array
(
[B] => B
)
)
)
)
Parent-child relationship nested Array
Fetch all the record from the database and creating nested array.
$data = SampleTable::find()->all();
$tree = buildTree($data);
print_r($tree);
public function buildTree(array $elements, $parentId = 0) {
$branch = array();
foreach ($elements as $element) {
if ($element['iParentId'] == $parentId) {
$children =buildTree($elements, $element['iCategoriesId']);
if ($children) {
$element['children'] = $children;
}
$branch[] = $element;
}
}
return $branch;
}
Print Categories and Sub-categories data in json Format
public static function buildTree(array $elements, $parentId = 0){
$branch = array();
foreach($elements as $element){
if($element['iParentId']==$parentId){
$children =buildTree($elements, $element['iCategoriesId']);
if ($children) {
$element['children'] = $children;
}
$branch[] = array(
'iCategoriesId' => $element->iCategoriesId,
'iParentId'=>$element->iParentId,
'vCategoriesName'=>$element->vCategoriesName,
'children'=>$element->children,
);
}
}
return[
$branch
];
}
$tree = array(
'H' => 'G',
'F' => 'G',
'G' => 'D',
'E' => 'D',
'A' => 'E',
'B' => 'C',
'C' => 'E',
'D' => null,
'Z' => null,
'MM' =>'Z',
'KK' =>'Z',
'MMM' =>'MM',
// 'MM'=>'DDD'
);
$aa=$this->parseTree($tree);
public function get_tress($tree,$key)
{
$x=array();
foreach ($tree as $keys => $value) {
if($value==$key){
$x[]=($keys);
}
}
echo "<li>";
foreach ($x as $ke => $val) {
echo "<ul>";
echo($val);
$this->get_tress($tree,$val);
echo "</ul>";
}
echo "</li>";
}
function parseTree($tree, $root = null) {
foreach ($tree as $key => $value) {
if($value==$root){
echo "<ul>";
echo($key);
$this->get_tress($tree,$key);
echo "</ul>";
}
}
Old question, but I too had to do this and the examples with recursion gave me a headache. In my database we have a locations table, which was a loca_id PK (Child) and self referencing loca_parent_id (Parent).
The aim is to represent this structure in HTML. The simple query can of couyrse return the data is some fixed order but I found not well enough to display such data in a natural way. What I really wanted was Oracle tree walk handling with LEVEL to help with display.
I decided to use the idea of a 'path' to uniquely identify each entry. For example:
Sorting the array by the path should make it easier to process for meaningful display.
I realise that use of associative arrays and sorts is cheating as it hides the recursive complexity of the operations, but to me this look simpler:
<table>
<?php
$sql = "
SELECT l.*,
pl.loca_name parent_loca_name,
'' loca_path
FROM locations l
LEFT JOIN locations pl ON l.loca_parent_id = pl.loca_id
ORDER BY l.loca_parent_id, l.loca_id
";
function print_row ( $rowdata )
{
?>
<tr>
<td>
<?=$rowdata['loca_id']?>
</td>
<td>
<?=$rowdata['loca_path']?>
</td>
<td>
<?=$rowdata['loca_type']?>
</td>
<td>
<?=$rowdata['loca_status']?>
</td>
</tr>
<?php
}
$stmt = $dbh->prepare($sql);
$stmt->execute();
$result = $stmt->get_result();
$data = $result->fetch_all(MYSQLI_ASSOC);
$printed = array();
// To get tree hierarchy usually means recursion of data.
// Here we will try to use an associate array and set a
// 'path' value to represent the hierarchy tree in one
// pass. Sorting this array by the path value should give
// a nice tree order and reference.
// The array key will be the unique id (loca_id) for each row.
// The value for each key will the complete row from the database.
// The row contains a element 'loca_path' - we will write the path
// for each row here. A child's path will be parent_path/child_name.
// For any child we encounter with a parent we look up the parents path
// using the loca_parent_id as the key.
// Caveat, although tested quickly, just make sure that all parents are
// returned first by the query.
foreach ($data as $row)
{
if ( $row['loca_parent_id'] == '' ) // Root Parent
{
$row['loca_path'] = $row['loca_name'] . '/';
$printed[$row['loca_id']] = $row;
}
else // Child/Sub-Parent
{
$row['loca_path'] = $printed[$row['loca_parent_id']]['loca_path'] . $row['loca_name'] . '/';
$printed[$row['loca_id']] = $row;
}
}
// Array with paths built, now sort then print
array_multisort(array_column($printed, 'loca_path'), SORT_ASC, $printed);
foreach ( $printed as $prow )
{
print_row ( $prow );
}
?>
</table>
How to Create Dynamic Tree View and Menu
Step 1:First we will Create treeview table in mysql database. this table contains four column.id is the task id and name is the task name.
-
-- Table structure for table `treeview_items`
--
CREATE TABLE IF NOT EXISTS `treeview_items` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(200) NOT NULL,
`title` varchar(200) NOT NULL,
`parent_id` varchar(11) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1 AUTO_INCREMENT=7 ;
--
-- Dumping data for table `treeview_items`
--
INSERT INTO `treeview_items` (`id`, `name`, `title`, `parent_id`) VALUES
(1, 'task1', 'task1title', '2'),
(2, 'task2', 'task2title', '0'),
(3, 'task3', 'task1title3', '0'),
(4, 'task4', 'task2title4', '3'),
(5, 'task4', 'task1title4', '3'),
(6, 'task5', 'task2title5', '5');
Step 2: Tree view recursive method
I have created below tree createTreeView() method which call recursive if current task id is greater than prev task id.
function createTreeView($array, $currentParent, $currLevel = 0, $prevLevel = -1) {
foreach ($array as $categoryId => $category) {
if ($currentParent == $category['parent_id']) {
if ($currLevel > $prevLevel) echo " <ol class='tree'> ";
if ($currLevel == $prevLevel) echo " </li> ";
echo '<li> <label for="subfolder2">'.$category['name'].'</label> <input type="checkbox" name="subfolder2"/>';
if ($currLevel > $prevLevel) { $prevLevel = $currLevel; }
$currLevel++;
createTreeView ($array, $categoryId, $currLevel, $prevLevel);
$currLevel--;
}
}
if ($currLevel == $prevLevel) echo " </li> </ol> ";
}
Step 3: Create index file to show tree view.
This is main file of treeview example here we will call createTreeView() method with required parameters.
<body>
<link rel="stylesheet" type="text/css" href="_styles.css" media="screen">
<?php
mysql_connect('localhost', 'root');
mysql_select_db('test');
$qry="SELECT * FROM treeview_items";
$result=mysql_query($qry);
$arrayCategories = array();
while($row = mysql_fetch_assoc($result)){
$arrayCategories[$row['id']] = array("parent_id" => $row['parent_id'], "name" =>
$row['name']);
}
?>
<div id="content" class="general-style1">
<?php
if(mysql_num_rows($result)!=0)
{
?>
<?php
createTreeView($arrayCategories, 0); ?>
<?php
}
?>
</div>
</body>
Step 4: Create CSS file style.css
Here we will write all css related class, currently I am using order list to create tree view. you can also change image path here.
img { border: none; }
input, select, textarea, th, td { font-size: 1em; }
/* CSS Tree menu styles */
ol.tree
{
padding: 0 0 0 30px;
width: 300px;
}
li
{
position: relative;
margin-left: -15px;
list-style: none;
}
li.file
{
margin-left: -1px !important;
}
li.file a
{
background: url(document.png) 0 0 no-repeat;
color: #fff;
padding-left: 21px;
text-decoration: none;
display: block;
}
li.file a[href *= '.pdf'] { background: url(document.png) 0 0 no-repeat; }
li.file a[href *= '.html'] { background: url(document.png) 0 0 no-repeat; }
li.file a[href $= '.css'] { background: url(document.png) 0 0 no-repeat; }
li.file a[href $= '.js'] { background: url(document.png) 0 0 no-repeat; }
li input
{
position: absolute;
left: 0;
margin-left: 0;
opacity: 0;
z-index: 2;
cursor: pointer;
height: 1em;
width: 1em;
top: 0;
}
li input + ol
{
background: url(toggle-small-expand.png) 40px 0 no-repeat;
margin: -0.938em 0 0 -44px; /* 15px */
height: 1em;
}
li input + ol > li { display: none; margin-left: -14px !important; padding-left: 1px; }
li label
{
background: url(folder-horizontal.png) 15px 1px no-repeat;
cursor: pointer;
display: block;
padding-left: 37px;
}
li input:checked + ol
{
background: url(toggle-small.png) 40px 5px no-repeat;
margin: -1.25em 0 0 -44px; /* 20px */
padding: 1.563em 0 0 80px;
height: auto;
}
li input:checked + ol > li { display: block; margin: 0 0 0.125em; /* 2px */}
li input:checked + ol > li:last-child { margin: 0 0 0.063em; /* 1px */ }
More Details

Flat array to tree [duplicate]

I have a bunch of name-parentname pairs, that I'd like to turn into as few heirarchical tree structures as possible. So for example, these could be the pairings:
Child : Parent
H : G
F : G
G : D
E : D
A : E
B : C
C : E
D : NULL
Which needs to be transformed into (a) heirarchical tree(s):
D
├── E
│ ├── A
│ │ └── B
│ └── C
└── G
├── F
└── H
The end result that I want is a nested set of <ul> elements, with each <li> containing the child's name.
There are no inconsistencies in the pairings (child is it's own parent, parent is child's child, etc), so a bunch of optimizations can probably be made.
How, in PHP, would I go from an array containing child=>parent pairs, to a set of Nested <ul>s?
I have a feeling that recursion is involved, but I'm not quite awake enough to think it through.
This requires a very basic recursive function to parse the child/parent pairs to a tree structure and another recursive function to print it out. Only one function would suffice but here's two for clarity (a combined function can be found at the end of this answer).
First initialize the array of child/parent pairs:
$tree = array(
'H' => 'G',
'F' => 'G',
'G' => 'D',
'E' => 'D',
'A' => 'E',
'B' => 'C',
'C' => 'E',
'D' => null
);
Then the function that parses that array into a hierarchical tree structure:
function parseTree($tree, $root = null) {
$return = array();
# Traverse the tree and search for direct children of the root
foreach($tree as $child => $parent) {
# A direct child is found
if($parent == $root) {
# Remove item from tree (we don't need to traverse this again)
unset($tree[$child]);
# Append the child into result array and parse its children
$return[] = array(
'name' => $child,
'children' => parseTree($tree, $child)
);
}
}
return empty($return) ? null : $return;
}
And a function that traverses that tree to print out an unordered list:
function printTree($tree) {
if(!is_null($tree) && count($tree) > 0) {
echo '<ul>';
foreach($tree as $node) {
echo '<li>'.$node['name'];
printTree($node['children']);
echo '</li>';
}
echo '</ul>';
}
}
And the actual usage:
$result = parseTree($tree);
printTree($result);
Here's the contents of $result:
Array(
[0] => Array(
[name] => D
[children] => Array(
[0] => Array(
[name] => G
[children] => Array(
[0] => Array(
[name] => H
[children] => NULL
)
[1] => Array(
[name] => F
[children] => NULL
)
)
)
[1] => Array(
[name] => E
[children] => Array(
[0] => Array(
[name] => A
[children] => NULL
)
[1] => Array(
[name] => C
[children] => Array(
[0] => Array(
[name] => B
[children] => NULL
)
)
)
)
)
)
)
)
If you want a bit more efficiency, you can combine those functions into one and reduce the number of iterations made:
function parseAndPrintTree($root, $tree) {
$return = array();
if(!is_null($tree) && count($tree) > 0) {
echo '<ul>';
foreach($tree as $child => $parent) {
if($parent == $root) {
unset($tree[$child]);
echo '<li>'.$child;
parseAndPrintTree($child, $tree);
echo '</li>';
}
}
echo '</ul>';
}
}
You'll only save 8 iterations on a dataset as small as this but on bigger sets it could make a difference.
Yet Another Function To Make A Tree (no recursion involved, uses references instead):
$array = array('H' => 'G', 'F' => 'G', ..., 'D' => null);
function to_tree($array)
{
$flat = array();
$tree = array();
foreach ($array as $child => $parent) {
if (!isset($flat[$child])) {
$flat[$child] = array();
}
if (!empty($parent)) {
$flat[$parent][$child] =& $flat[$child];
} else {
$tree[$child] =& $flat[$child];
}
}
return $tree;
}
Returns an hierarchical array like this one:
Array(
[D] => Array(
[G] => Array(
[H] => Array()
[F] => Array()
)
...
)
)
Which can easily be printed as a HTML list using recursive function.
Another, more simplified way to convert the flat structure in the $tree into a hierarchy. Only one temporary array is needed to expose it:
// add children to parents
$flat = array(); # temporary array
foreach ($tree as $name => $parent)
{
$flat[$name]['name'] = $name; # self
if (NULL === $parent)
{
# no parent, is root element, assign it to $tree
$tree = &$flat[$name];
}
else
{
# has parent, add self as child
$flat[$parent]['children'][] = &$flat[$name];
}
}
unset($flat);
That's all for getting the hierarchy into a multidimensional array:
Array
(
[children] => Array
(
[0] => Array
(
[children] => Array
(
[0] => Array
(
[name] => H
)
[1] => Array
(
[name] => F
)
)
[name] => G
)
[1] => Array
(
[name] => E
[children] => Array
(
[0] => Array
(
[name] => A
)
[1] => Array
(
[children] => Array
(
[0] => Array
(
[name] => B
)
)
[name] => C
)
)
)
)
[name] => D
)
The output is less trivial if you want to avoid recursion (can be a burden with large structures).
I always wanted to solve the UL/LI "dilemma" for outputting an array. The dilemma is, that each item does not know whether or not children will follow up or how many preceding elements need to be closed. In another answer I already solved that by using a RecursiveIteratorIterator and looking for getDepth() and other meta-information that my own written Iterator provided: Getting nested set model into a <ul> but hiding “closed” subtrees. That answer shows as well that with iterators you're quite flexible.
However that was a pre-sorted list, so would not be suitable for your example. Additionally I always wanted to solve this for a sort of standard tree structure and HTML's <ul> and <li> elements.
The basic concept I came up is the following:
TreeNode - Abstracts each element into a simple TreeNode type that can provide it's value (e.g. Name) and whether or not it has children.
TreeNodesIterator - A RecursiveIterator that is able to iterate over a set (array) of these TreeNodes. That is fairly simple as the TreeNode type already knows if it has children and which ones.
RecursiveListIterator - A RecursiveIteratorIterator that has all the events needed when it recursively iterate over any kind of RecursiveIterator:
beginIteration / endIteration - Begin and end of the main list.
beginElement / endElement - Begin and end of each element.
beginChildren / endChildren - Begin and end of each children list.
This RecursiveListIterator only provides these events in a form of function calls. children lists, as it is typical for <ul><li> lists, are opened and closed inside it's parent <li> element. Therefore the endElement event is fired after the according endChildren event. This could be changed or made configurable to broaden the use this class. The events are distributed as function calls to a decorator object then, to keep things apart.
ListDecorator - A "decorator" class that is just a receiver of the events of RecursiveListIterator.
I start with the main output logic. Taken the now hierarchical $tree array, the final code looks like the following:
$root = new TreeNode($tree);
$it = new TreeNodesIterator(array($root));
$rit = new RecursiveListIterator($it);
$decor = new ListDecorator($rit);
$rit->addDecorator($decor);
foreach($rit as $item)
{
$inset = $decor->inset(1);
printf("%s%s\n", $inset, $item->getName());
}
First let's look into the ListDecorator that simply wraps the <ul> and <li> elements and is deciding about how the list structure is output:
class ListDecorator
{
private $iterator;
public function __construct(RecursiveListIterator $iterator)
{
$this->iterator = $iterator;
}
public function inset($add = 0)
{
return str_repeat(' ', $this->iterator->getDepth()*2+$add);
}
The constructor takes the list iterator it's working on. inset is just a helper function for nice indentation of the output. The rest are just the output functions for each event:
public function beginElement()
{
printf("%s<li>\n", $this->inset());
}
public function endElement()
{
printf("%s</li>\n", $this->inset());
}
public function beginChildren()
{
printf("%s<ul>\n", $this->inset(-1));
}
public function endChildren()
{
printf("%s</ul>\n", $this->inset(-1));
}
public function beginIteration()
{
printf("%s<ul>\n", $this->inset());
}
public function endIteration()
{
printf("%s</ul>\n", $this->inset());
}
}
With these output functions in mind, this is the main output wrap-up / loop again, I go through it step by step:
$root = new TreeNode($tree);
Create the root TreeNode which will be used to start iteration upon:
$it = new TreeNodesIterator(array($root));
This TreeNodesIterator is a RecursiveIterator that enables recursive iteration over the single $root node. It is passed as an array because that class needs something to iterate over and allows re-use with a set of children which is also an array of TreeNode elements.
$rit = new RecursiveListIterator($it);
This RecursiveListIterator is a RecursiveIteratorIterator that provides the said events. To make use of it, only a ListDecorator needs to be provided (the class above) and assigned with addDecorator:
$decor = new ListDecorator($rit);
$rit->addDecorator($decor);
Then everything is set-up to just foreach over it and output each node:
foreach($rit as $item)
{
$inset = $decor->inset(1);
printf("%s%s\n", $inset, $item->getName());
}
As this example shows, the whole output logic is encapsulated in the ListDecorator class and this single foreach. The whole recursive traversal has been fully encapsulated into SPL recursive iterators which provided a stacked procedure, that means internally no recursion function calls are done.
The event based ListDecorator allows you to modify the output specifically and to provide multiple type of lists for the same data structure. It's even possible to change the input as the array data has been encapsulated into TreeNode.
The full code example:
<?php
namespace My;
$tree = array('H' => 'G', 'F' => 'G', 'G' => 'D', 'E' => 'D', 'A' => 'E', 'B' => 'C', 'C' => 'E', 'D' => null);
// add children to parents
$flat = array(); # temporary array
foreach ($tree as $name => $parent)
{
$flat[$name]['name'] = $name; # self
if (NULL === $parent)
{
# no parent, is root element, assign it to $tree
$tree = &$flat[$name];
}
else
{
# has parent, add self as child
$flat[$parent]['children'][] = &$flat[$name];
}
}
unset($flat);
class TreeNode
{
protected $data;
public function __construct(array $element)
{
if (!isset($element['name']))
throw new InvalidArgumentException('Element has no name.');
if (isset($element['children']) && !is_array($element['children']))
throw new InvalidArgumentException('Element has invalid children.');
$this->data = $element;
}
public function getName()
{
return $this->data['name'];
}
public function hasChildren()
{
return isset($this->data['children']) && count($this->data['children']);
}
/**
* #return array of child TreeNode elements
*/
public function getChildren()
{
$children = $this->hasChildren() ? $this->data['children'] : array();
$class = get_called_class();
foreach($children as &$element)
{
$element = new $class($element);
}
unset($element);
return $children;
}
}
class TreeNodesIterator implements \RecursiveIterator
{
private $nodes;
public function __construct(array $nodes)
{
$this->nodes = new \ArrayIterator($nodes);
}
public function getInnerIterator()
{
return $this->nodes;
}
public function getChildren()
{
return new TreeNodesIterator($this->nodes->current()->getChildren());
}
public function hasChildren()
{
return $this->nodes->current()->hasChildren();
}
public function rewind()
{
$this->nodes->rewind();
}
public function valid()
{
return $this->nodes->valid();
}
public function current()
{
return $this->nodes->current();
}
public function key()
{
return $this->nodes->key();
}
public function next()
{
return $this->nodes->next();
}
}
class RecursiveListIterator extends \RecursiveIteratorIterator
{
private $elements;
/**
* #var ListDecorator
*/
private $decorator;
public function addDecorator(ListDecorator $decorator)
{
$this->decorator = $decorator;
}
public function __construct($iterator, $mode = \RecursiveIteratorIterator::SELF_FIRST, $flags = 0)
{
parent::__construct($iterator, $mode, $flags);
}
private function event($name)
{
// event debug code: printf("--- %'.-20s --- (Depth: %d, Element: %d)\n", $name, $this->getDepth(), #$this->elements[$this->getDepth()]);
$callback = array($this->decorator, $name);
is_callable($callback) && call_user_func($callback);
}
public function beginElement()
{
$this->event('beginElement');
}
public function beginChildren()
{
$this->event('beginChildren');
}
public function endChildren()
{
$this->testEndElement();
$this->event('endChildren');
}
private function testEndElement($depthOffset = 0)
{
$depth = $this->getDepth() + $depthOffset;
isset($this->elements[$depth]) || $this->elements[$depth] = 0;
$this->elements[$depth] && $this->event('endElement');
}
public function nextElement()
{
$this->testEndElement();
$this->event('{nextElement}');
$this->event('beginElement');
$this->elements[$this->getDepth()] = 1;
}
public function beginIteration()
{
$this->event('beginIteration');
}
public function endIteration()
{
$this->testEndElement();
$this->event('endIteration');
}
}
class ListDecorator
{
private $iterator;
public function __construct(RecursiveListIterator $iterator)
{
$this->iterator = $iterator;
}
public function inset($add = 0)
{
return str_repeat(' ', $this->iterator->getDepth()*2+$add);
}
public function beginElement()
{
printf("%s<li>\n", $this->inset(1));
}
public function endElement()
{
printf("%s</li>\n", $this->inset(1));
}
public function beginChildren()
{
printf("%s<ul>\n", $this->inset());
}
public function endChildren()
{
printf("%s</ul>\n", $this->inset());
}
public function beginIteration()
{
printf("%s<ul>\n", $this->inset());
}
public function endIteration()
{
printf("%s</ul>\n", $this->inset());
}
}
$root = new TreeNode($tree);
$it = new TreeNodesIterator(array($root));
$rit = new RecursiveListIterator($it);
$decor = new ListDecorator($rit);
$rit->addDecorator($decor);
foreach($rit as $item)
{
$inset = $decor->inset(2);
printf("%s%s\n", $inset, $item->getName());
}
Outpupt:
<ul>
<li>
D
<ul>
<li>
G
<ul>
<li>
H
</li>
<li>
F
</li>
</ul>
</li>
<li>
E
<ul>
</li>
<li>
A
</li>
<li>
C
<ul>
<li>
B
</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
</ul>
Demo (PHP 5.2 variant)
A possible variant would be an iterator that iterates over any RecursiveIterator and provides an iteration over all events that can occur. An switch / case inside the foreach loop could then deal with the events.
Related:
Nested List with RecursiveArrayIterator
The RecursiveIteratorIterator class
While Alexander-Konstantinov's solution might not seem as easy to read at first, it is both genius and exponentially better in terms of performance, this should have been voted as the best answer.
Thanks mate, I made a benchmark in your honor to compare the 2 solutions presented in this post.
I had an #250k flat tree with 6 levels that I had to convert and I was searching for a better way to do so and avoid recursive iterations.
Recursion vs Reference:
// Generate a 6 level flat tree
$root = null;
$lvl1 = 13;
$lvl2 = 11;
$lvl3 = 7;
$lvl4 = 5;
$lvl5 = 3;
$lvl6 = 1;
$flatTree = [];
for ($i = 1; $i <= 450000; $i++) {
if ($i % 3 == 0) { $lvl5 = $i; $flatTree[$lvl6] = $lvl5; continue; }
if ($i % 5 == 0) { $lvl4 = $i; $flatTree[$lvl5] = $lvl4; continue; }
if ($i % 7 == 0) { $lvl3 = $i; $flatTree[$lvl3] = $lvl2; continue; }
if ($i % 11 == 0) { $lvl2 = $i; $flatTree[$lvl2] = $lvl1; continue; }
if ($i % 13 == 0) { $lvl1 = $i; $flatTree[$lvl1] = $root; continue; }
$lvl6 = $i;
}
echo 'Array count: ', count($flatTree), PHP_EOL;
// Reference function
function treeByReference($flatTree)
{
$flat = [];
$tree = [];
foreach ($flatTree as $child => $parent) {
if (!isset($flat[$child])) {
$flat[$child] = [];
}
if (!empty($parent)) {
$flat[$parent][$child] =& $flat[$child];
} else {
$tree[$child] =& $flat[$child];
}
}
return $tree;
}
// Recursion function
function treeByRecursion($flatTree, $root = null)
{
$return = [];
foreach($flatTree as $child => $parent) {
if ($parent == $root) {
unset($flatTree[$child]);
$return[$child] = treeByRecursion($flatTree, $child);
}
}
return $return ?: [];
}
// Benchmark reference
$t1 = microtime(true);
$tree = treeByReference($flatTree);
echo 'Reference: ', (microtime(true) - $t1), PHP_EOL;
// Benchmark recursion
$t2 = microtime(true);
$tree = treeByRecursion($flatTree);
echo 'Recursion: ', (microtime(true) - $t2), PHP_EOL;
The output speaks for itself:
Array count: 255493
Reference: 0.3259289264679 (less than 0.4s)
Recursion: 6604.9865279198 (almost 2h)
Well, first I would turn the straight array of key-value pairs into a hierarchical array
function convertToHeiarchical(array $input) {
$parents = array();
$root = array();
$children = array();
foreach ($input as $item) {
$parents[$item['id']] = &$item;
if ($item['parent_id']) {
if (!isset($children[$item['parent_id']])) {
$children[$item['parent_id']] = array();
}
$children[$item['parent_id']][] = &$item;
} else {
$root = $item['id'];
}
}
foreach ($parents as $id => &$item) {
if (isset($children[$id])) {
$item['children'] = $children[$id];
} else {
$item['children'] = array();
}
}
return $parents[$root];
}
That'll can convert a flat array with parent_id and id into a hierarchical one:
$item = array(
'id' => 'A',
'blah' => 'blah',
'children' => array(
array(
'id' => 'B',
'blah' => 'blah',
'children' => array(
array(
'id' => 'C',
'blah' => 'blah',
'children' => array(),
),
),
'id' => 'D',
'blah' => 'blah',
'children' => array(
array(
'id' => 'E',
'blah' => 'blah',
'children' => array(),
),
),
),
),
);
Then, just create a rendering function:
function renderItem($item) {
$out = "Your OUtput For Each Item Here";
$out .= "<ul>";
foreach ($item['children'] as $child) {
$out .= "<li>".renderItem($child)."</li>";
}
$out .= "</ul>";
return $out;
}
Well, to parse into ULs and LIs, it would be something like:
$array = array (
'H' => 'G'
'F' => 'G'
'G' => 'D'
'E' => 'D'
'A' => 'E'
'B' => 'C'
'C' => 'E'
'D' => 'NULL'
);
recurse_uls ($array, 'NULL');
function recurse_uls ($array, $parent)
{
echo '<ul>';
foreach ($array as $c => $p) {
if ($p != $parent) continue;
echo '<li>'.$c.'</li>';
recurse_uls ($array, $c);
}
echo '</ul>';
}
But I'd love to see a solution that doesn't require you to iterate through the array so often...
Here's what I came up with:
$arr = array(
'H' => 'G',
'F' => 'G',
'G' => 'D',
'E' => 'D',
'A' => 'E',
'B' => 'C',
'C' => 'E',
'D' => null );
$nested = parentChild($arr);
print_r($nested);
function parentChild(&$arr, $parent = false) {
if( !$parent) { //initial call
$rootKey = array_search( null, $arr);
return array($rootKey => parentChild($arr, $rootKey));
}else { // recursing through
$keys = array_keys($arr, $parent);
$piece = array();
if($keys) { // found children, so handle them
if( !is_array($keys) ) { // only one child
$piece = parentChild($arr, $keys);
}else{ // multiple children
foreach( $keys as $key ){
$piece[$key] = parentChild($arr, $key);
}
}
}else {
return $parent; //return the main tag (no kids)
}
return $piece; // return the array built via recursion
}
}
outputs:
Array
(
[D] => Array
(
[G] => Array
(
[H] => H
[F] => F
)
[E] => Array
(
[A] => A
[C] => Array
(
[B] => B
)
)
)
)
Parent-child relationship nested Array
Fetch all the record from the database and creating nested array.
$data = SampleTable::find()->all();
$tree = buildTree($data);
print_r($tree);
public function buildTree(array $elements, $parentId = 0) {
$branch = array();
foreach ($elements as $element) {
if ($element['iParentId'] == $parentId) {
$children =buildTree($elements, $element['iCategoriesId']);
if ($children) {
$element['children'] = $children;
}
$branch[] = $element;
}
}
return $branch;
}
Print Categories and Sub-categories data in json Format
public static function buildTree(array $elements, $parentId = 0){
$branch = array();
foreach($elements as $element){
if($element['iParentId']==$parentId){
$children =buildTree($elements, $element['iCategoriesId']);
if ($children) {
$element['children'] = $children;
}
$branch[] = array(
'iCategoriesId' => $element->iCategoriesId,
'iParentId'=>$element->iParentId,
'vCategoriesName'=>$element->vCategoriesName,
'children'=>$element->children,
);
}
}
return[
$branch
];
}
$tree = array(
'H' => 'G',
'F' => 'G',
'G' => 'D',
'E' => 'D',
'A' => 'E',
'B' => 'C',
'C' => 'E',
'D' => null,
'Z' => null,
'MM' =>'Z',
'KK' =>'Z',
'MMM' =>'MM',
// 'MM'=>'DDD'
);
$aa=$this->parseTree($tree);
public function get_tress($tree,$key)
{
$x=array();
foreach ($tree as $keys => $value) {
if($value==$key){
$x[]=($keys);
}
}
echo "<li>";
foreach ($x as $ke => $val) {
echo "<ul>";
echo($val);
$this->get_tress($tree,$val);
echo "</ul>";
}
echo "</li>";
}
function parseTree($tree, $root = null) {
foreach ($tree as $key => $value) {
if($value==$root){
echo "<ul>";
echo($key);
$this->get_tress($tree,$key);
echo "</ul>";
}
}
Old question, but I too had to do this and the examples with recursion gave me a headache. In my database we have a locations table, which was a loca_id PK (Child) and self referencing loca_parent_id (Parent).
The aim is to represent this structure in HTML. The simple query can of couyrse return the data is some fixed order but I found not well enough to display such data in a natural way. What I really wanted was Oracle tree walk handling with LEVEL to help with display.
I decided to use the idea of a 'path' to uniquely identify each entry. For example:
Sorting the array by the path should make it easier to process for meaningful display.
I realise that use of associative arrays and sorts is cheating as it hides the recursive complexity of the operations, but to me this look simpler:
<table>
<?php
$sql = "
SELECT l.*,
pl.loca_name parent_loca_name,
'' loca_path
FROM locations l
LEFT JOIN locations pl ON l.loca_parent_id = pl.loca_id
ORDER BY l.loca_parent_id, l.loca_id
";
function print_row ( $rowdata )
{
?>
<tr>
<td>
<?=$rowdata['loca_id']?>
</td>
<td>
<?=$rowdata['loca_path']?>
</td>
<td>
<?=$rowdata['loca_type']?>
</td>
<td>
<?=$rowdata['loca_status']?>
</td>
</tr>
<?php
}
$stmt = $dbh->prepare($sql);
$stmt->execute();
$result = $stmt->get_result();
$data = $result->fetch_all(MYSQLI_ASSOC);
$printed = array();
// To get tree hierarchy usually means recursion of data.
// Here we will try to use an associate array and set a
// 'path' value to represent the hierarchy tree in one
// pass. Sorting this array by the path value should give
// a nice tree order and reference.
// The array key will be the unique id (loca_id) for each row.
// The value for each key will the complete row from the database.
// The row contains a element 'loca_path' - we will write the path
// for each row here. A child's path will be parent_path/child_name.
// For any child we encounter with a parent we look up the parents path
// using the loca_parent_id as the key.
// Caveat, although tested quickly, just make sure that all parents are
// returned first by the query.
foreach ($data as $row)
{
if ( $row['loca_parent_id'] == '' ) // Root Parent
{
$row['loca_path'] = $row['loca_name'] . '/';
$printed[$row['loca_id']] = $row;
}
else // Child/Sub-Parent
{
$row['loca_path'] = $printed[$row['loca_parent_id']]['loca_path'] . $row['loca_name'] . '/';
$printed[$row['loca_id']] = $row;
}
}
// Array with paths built, now sort then print
array_multisort(array_column($printed, 'loca_path'), SORT_ASC, $printed);
foreach ( $printed as $prow )
{
print_row ( $prow );
}
?>
</table>
How to Create Dynamic Tree View and Menu
Step 1:First we will Create treeview table in mysql database. this table contains four column.id is the task id and name is the task name.
-
-- Table structure for table `treeview_items`
--
CREATE TABLE IF NOT EXISTS `treeview_items` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(200) NOT NULL,
`title` varchar(200) NOT NULL,
`parent_id` varchar(11) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1 AUTO_INCREMENT=7 ;
--
-- Dumping data for table `treeview_items`
--
INSERT INTO `treeview_items` (`id`, `name`, `title`, `parent_id`) VALUES
(1, 'task1', 'task1title', '2'),
(2, 'task2', 'task2title', '0'),
(3, 'task3', 'task1title3', '0'),
(4, 'task4', 'task2title4', '3'),
(5, 'task4', 'task1title4', '3'),
(6, 'task5', 'task2title5', '5');
Step 2: Tree view recursive method
I have created below tree createTreeView() method which call recursive if current task id is greater than prev task id.
function createTreeView($array, $currentParent, $currLevel = 0, $prevLevel = -1) {
foreach ($array as $categoryId => $category) {
if ($currentParent == $category['parent_id']) {
if ($currLevel > $prevLevel) echo " <ol class='tree'> ";
if ($currLevel == $prevLevel) echo " </li> ";
echo '<li> <label for="subfolder2">'.$category['name'].'</label> <input type="checkbox" name="subfolder2"/>';
if ($currLevel > $prevLevel) { $prevLevel = $currLevel; }
$currLevel++;
createTreeView ($array, $categoryId, $currLevel, $prevLevel);
$currLevel--;
}
}
if ($currLevel == $prevLevel) echo " </li> </ol> ";
}
Step 3: Create index file to show tree view.
This is main file of treeview example here we will call createTreeView() method with required parameters.
<body>
<link rel="stylesheet" type="text/css" href="_styles.css" media="screen">
<?php
mysql_connect('localhost', 'root');
mysql_select_db('test');
$qry="SELECT * FROM treeview_items";
$result=mysql_query($qry);
$arrayCategories = array();
while($row = mysql_fetch_assoc($result)){
$arrayCategories[$row['id']] = array("parent_id" => $row['parent_id'], "name" =>
$row['name']);
}
?>
<div id="content" class="general-style1">
<?php
if(mysql_num_rows($result)!=0)
{
?>
<?php
createTreeView($arrayCategories, 0); ?>
<?php
}
?>
</div>
</body>
Step 4: Create CSS file style.css
Here we will write all css related class, currently I am using order list to create tree view. you can also change image path here.
img { border: none; }
input, select, textarea, th, td { font-size: 1em; }
/* CSS Tree menu styles */
ol.tree
{
padding: 0 0 0 30px;
width: 300px;
}
li
{
position: relative;
margin-left: -15px;
list-style: none;
}
li.file
{
margin-left: -1px !important;
}
li.file a
{
background: url(document.png) 0 0 no-repeat;
color: #fff;
padding-left: 21px;
text-decoration: none;
display: block;
}
li.file a[href *= '.pdf'] { background: url(document.png) 0 0 no-repeat; }
li.file a[href *= '.html'] { background: url(document.png) 0 0 no-repeat; }
li.file a[href $= '.css'] { background: url(document.png) 0 0 no-repeat; }
li.file a[href $= '.js'] { background: url(document.png) 0 0 no-repeat; }
li input
{
position: absolute;
left: 0;
margin-left: 0;
opacity: 0;
z-index: 2;
cursor: pointer;
height: 1em;
width: 1em;
top: 0;
}
li input + ol
{
background: url(toggle-small-expand.png) 40px 0 no-repeat;
margin: -0.938em 0 0 -44px; /* 15px */
height: 1em;
}
li input + ol > li { display: none; margin-left: -14px !important; padding-left: 1px; }
li label
{
background: url(folder-horizontal.png) 15px 1px no-repeat;
cursor: pointer;
display: block;
padding-left: 37px;
}
li input:checked + ol
{
background: url(toggle-small.png) 40px 5px no-repeat;
margin: -1.25em 0 0 -44px; /* 20px */
padding: 1.563em 0 0 80px;
height: auto;
}
li input:checked + ol > li { display: block; margin: 0 0 0.125em; /* 2px */}
li input:checked + ol > li:last-child { margin: 0 0 0.063em; /* 1px */ }
More Details

Multi-Dimensional Navigation Array

Trying to construct a navigation using multi-dimensional arrays and recursion. I have the following code:
First I run <?php $title = 'pagename'; ?> on each individual page beneath doctype (for active class detection)
ARRAY:
<?php
$nav_array = array ('Home' => 'index.php',
'About' => array ( 'about.php', array (
'Michael' => array( 'michael.php', array (
'Blog' => 'blog.php',
'Portfolio' => 'portfolio.php')),
'Aaron' => 'aaron.php' ,
'Kenny' => 'kenny.php',
'David'=> 'david.php')),
'Services' => array ( 'services.php', array (
'Get Noticed' => 'getnoticed.php',
'Hosting' => 'hosting.php')),
'Clients' => 'clients.php',
'Contact Us' => 'contact.php'
);
$base = basename($_SERVER['PHP_SELF']);
?>
FOREACH: (generates nav)
<ul>
<?php
foreach ($nav_array as $k => $v) {
echo buildLinks ($k, $v, $base);
}
?>
</ul>
buildLinks:
<?php // Building the links
function buildLinks ($label_name, $file_name, $active_class) {
if ($label_name == $title) {
$theLink = "<li><a class=\"selected\" href=\"$file_name\">$label_name</a></li>\n";
} else {
$theLink = "<li>$label_name</li>\n";
}
return $theLink;
}
?>
Result: http://khill.mhostiuckproductions.com/siteLSSBoilerPlate/arraytest.php
The sub menu's will appear on the hover of parent element using CSS. I need to be able to fall through multiple sub-levels without modifying anything but the array.
How do I make my foreach fall through the rest of the array recursively?
(Note: that I have to have the ability to apply a class of active to current pages, and a class of arrow to parent elements that have a sub-menu present.)
No matter what data structure you use to build your navigation, you'll need to make your function recursive, here's a quick and dirty way:
echo "<ul>";
foreach ($nav_array as $nav_title => $nav_data) {
echo buildLinks($nav_title, $nav_data, $base, $title);
}
echo "</ul>";
/* NOTE that we pass $title to the function */
function buildLinks ($label_name, $file_name, $active_class, $title) {
$theLink = '';
/* this is dirty code, you should reconsider your data structure */
$navigation_list = false;
if (is_array($file_name)) {
$navigation_list = $file_name[1];
$file_name = $file_name[0];
}
if ($active_class == $title) {
$theLink = "<li><a class=\"selected\" href=\"$file_name\">$label_name</a></li>\n";
} else {
$theLink = "<li>$label_name</li>\n";
}
if ($navigation_list) {
$theLink .= "<ul>";
foreach ($navigation_list as $nav_title => $nav_data) {
$theLink .= buildLinks($nav_title, $nav_data, $active_class, $title);
}
$theLink .= "</ul>";
}
return $theLink;
}
Not a clean solution in anyway, if I were you I'd change the data structure to be an easier one to handle.
I think this is a very bad way. I recommend to save your menu elements as XML or JSON and use parsers. It will facilitate your work.

How to build a tree from a concatenated string in PHP?

I try to make a tree list in PHP from a hierarchy stored in a concatenated string in my mysql database
This my table :
and I'd like to reproduce something like this :
<ul>
<li>
<ul>
<li></li>
<li>
<ul>
<li></li>
<li></li>
</ul>
</li>
<li></li>
</ul>
</li>
<li></li>
<li></li>
</ul>
I know I have to use a recursive function I don't reach to do...
Maybe someone could help me
code without comments
see the usage and dataset section below to see what you need to pass and how to use these functions:
function items_to_tree( $items ){
$array = array();
foreach( $items as $item ) {
$parts = explode('.', $item['hierarchy']);
$last = array_pop( $parts );
$cursor = &$array;
foreach ( $parts as $part ) {
if ( !is_array($cursor[$part]) ) {
$cursor[$part] = array();
}
$cursor = &$cursor[$part];
}
$cursor[$last]['#item'] = $item;
}
return $array;
}
function tree_to_ul( $tree ){
$html = $children = '';
foreach( $tree as $key => $item ){
if ( substr($key,0,1) == '#' ) continue;
$children .= tree_to_ul( $item );
}
if ( isset($tree['#item']) ) {
$html .= '<li>' . PHP_EOL;
$html .= '<em>' . $tree['#item']['menu_text'] . '</em>' . PHP_EOL;
$html .= ( $children ? '<ul>' . $children . '</ul>' . PHP_EOL : '' );
$html .= '</li>' . PHP_EOL;
return $html;
}
else {
return $children;
}
}
code with comments and explanation
The code to convert your items to a tree structure:
function items_to_tree( $items ){
$array = array();
foreach( $items as $item ) {
/// split each hierarchy string into it's dot separated parts
$parts = explode('.', $item['hierarchy']);
/// pop off the last item of the array, we'll use this for assignment later
$last = array_pop( $parts );
/// create a reference to our position in the array we wish to fill out
$cursor = &$array;
/// step each hierarchy part and travel down the array structure,
/// just like you would if you typed an array path manually.
/// i.e. $array[$part][$part][...] and so on
foreach ( $parts as $part ) {
/// if at this point in the array, we don't have an array, make one.
if ( !is_array($cursor[$part]) ) {
$cursor[$part] = array();
}
/// ready for the next step, shift our reference to point to the next
/// $part in the array chain. e.g. if $cursor pointed to `$array[$part]`
/// before, after the next line of code the $cursor will point
/// to `$array[$oldpart][$part]`
$cursor = &$cursor[$part];
}
/// we popped the last item off the $parts array so we could easily
/// assign our final value to where the $cursor ends up pointing to.
/// starting with a hierarchy of '00001.00002.00003' would mean at this
/// point $cursor points to $array['00001']['00002'] and $last = '00003';
/// so finally we get $array['00001']['00002']['00003']['#item'] = $item;
$cursor[$last]['#item'] = $item;
/// use '#item' to keep our item's information separate from it's children.
}
/// return our built up array.
return $array;
}
The code to convert the tree structure to a UL:
function tree_to_ul( $tree ){
/// start with nothing
$html = $children = '';
/// step each item found in the current level of $tree
foreach( $tree as $key => $item ){
/// if the item's key starts with a # skip, these contain
/// our item's information and should not be treated as children
if ( substr($key,0,1) == '#' ) continue;
/// recurse this function so that we do the same for any child # any level.
$children .= tree_to_ul( $item );
}
/// if at this level a #item has been set, use this item information to
/// add a title to our level. You could change this to add whatever info
/// from your original database item that you'd like.
if ( isset($tree['#item']) ) {
$html .= '<li>' . PHP_EOL;
$html .= '<em>' . $tree['#item']['menu_text'] . '</em>' . PHP_EOL;
$html .= ( $children ? '<ul>' . $children . '</ul>' . PHP_EOL : '' );
$html .= '</li>' . PHP_EOL;
return $html;
}
/// if there wasn't an item, just return the traversed children.
else {
return $children;
}
}
dataset:
/// I simplified your dataset to an array, this could easily be generated
/// from a database query. You could also convert my code so that you
/// don't have to pre-generate an array, and instead could process after
/// each fetch from the database.
$items = array(
array('hierarchy' => '00001', 'menu_text' => 'One'),
array('hierarchy' => '00002', 'menu_text' => 'Two'),
array('hierarchy' => '00002.00001', 'menu_text' => 'Three'),
array('hierarchy' => '00002.00002', 'menu_text' => 'Four'),
array('hierarchy' => '00002.00003', 'menu_text' => 'Five'),
array('hierarchy' => '00002.00004', 'menu_text' => 'Six'),
array('hierarchy' => '00003', 'menu_text' => 'Seven'),
array('hierarchy' => '00003.00001', 'menu_text' => 'Eight'),
array('hierarchy' => '00003.00001.00001', 'menu_text' => 'Nine'),
array('hierarchy' => '00003.00001.00002', 'menu_text' => 'Ten'),
array('hierarchy' => '00003.00001.00003', 'menu_text' => 'Eleven'),
array('hierarchy' => '00003.00002', 'menu_text' => 'Twelve'),
);
usage:
/// Simple usage :) if a little complex explanation
$tree = items_to_tree( $items );
$html = tree_to_ul( $tree );
echo $html;
in the interests of codegolf ;)
The following could replace my items_to_tree function -- however it isn't advised.
$a = array();
foreach($items as $i){
eval('$a["'.str_replace('.','"]["',$i['hierarchy']).'"]=array("#item"=>$i);');
}
$refs = new stdClass();
//Assuming $data is the result array of your query to fetch the data.
foreach($data as $result)
{
$name = $result['hierarchy'];
$parent = substr($result['hierarchy'],0,strrpos($result['hierarchy'],'.'));
$thisref = &$refs->{$name};
foreach($result as $k => $v)
{
$thisref->{$k} = $v;
}
if ($parent == '') {
$tree->{$name} = &$thisref;
} else {
$refs->{$parent}->children->{$name} = &$thisref;
}
}
This will give you a nice object with every node's child in the property children.
function drawUL($level){
echo '<ul>';
foreach($level as $li){
echo '<li>'.$li->label;
if(isset($li->children))drawUl($li->children);
echo '</li>';
}
echo '</ul>';
}
drawUl($tree);

Generate navigation from a multi-dimensional array

The question: How do I generate navigation, allowing for applying different classes to different sub-items, from a multi-dimensional array?
Here is how I was doing it before I had any need for multi-level navigation:
Home
Pics
About
and was generated by calling nav():
function nav(){
$links = array(
"Home" => "home.php",
"Pics" => "pics.php",
"About" => "about.php"
);
$base = basename($_SERVER['PHP_SELF']);
foreach($nav as $k => $v){
echo buildLinks($k, $v, $base);
}
}
Here is buildLinks():
function buildLinks($name, $page, $selected){
if($selected == $page){
$theLink = "<li class=\"selected\">$name</li>\n";
} else {
$thelink = "<li>$name</li>\n";
}
return $thelink;
}
My question, again:
how would I achieve the following nav (and notice that the visible sub navigation elements are only present when on that specific page):
Home
something1
something2
Pics
About
and...
Home
Pics
people
places
About
What I've tried
From looking at it it would seem that some iterator in the SPL would be a good fit for this but I'm not sure how to approach this. I have played around with RecursiveIteratorIterator but I'm not sure how to apply a different style to only the sub menu items and also how to only show these items if you are on the correct page.
I built this array to test with but don't know how to work with the submenu1 items individually:
$nav = array(
array(
"Home" => "home.php",
"submenu1" => array(
"something1"=>"something1.php",
"something2" => "something2.php")
),
array("Pics" => "pics.php"),
array("About" => "about.php")
);
The following will print out the lot in order but how do I apply, say a class name to the submenu1 items or only show them when the person is on, say, the "Home" page?
$iterator = new RecursiveIteratorIterator(new RecursiveArrayIterator($nav));
foreach($iterator as $key=>$value) {
echo $key.' -- '.$value.'<br />';
}
And this gets me:
Home
something1
something2
Pics
About
But I have no way to apply classes to those sub items and no way to only display them conditionally because I don't see how to target just these elements.
Don't reinvent the wheel, use Zend_Navigation and you will be happy.
You were on the right track with RecursiveIteratorIterator. It essentially flattens a recursive iterator. Here is the correct way:
$nav = array(
array(
"Home" => "home.php",
"submenu1" => array(
"something1"=>"something1.php",
"something2" => "something2.php")
),
array("Pics" => "pics.php"),
array("About" => "about.php"),
);
$it = new RecursiveIteratorIterator(
new RecursiveArrayIterator($nav),
RecursiveIteratorIterator::SELF_FIRST
);
foreach ($it as $k => $v) {
if ($it->getDepth() == 0)
continue;
echo str_repeat(" ", $it->getDepth() - 1) .
"$k => $v\n";
}
gives
Home => home.php
submenu1 => Array
something1 => something1.php
something2 => something2.php
Pics => pics.php
About => about.php
It seems like you might want to do this in a more object oriented way.
If not, it seems like you should at least define an algorithm that makes sense, right now you are just blindly guessing. Instead, DEFINE.
For example:
I am defining my navigation to be a php hash based tree. A navigation item will have the following:
A) if there is a top level link, the array hash will contain an item(sub array) labeled "navigation leaf"
b) A navigation Leaf will contain elements labeled "Display value", "link value", and "alt value". These items will be used to generate an anchor tag.
c) if an element has a submenu, in addition to containing a "Navigation Leaf", a "subnavigation" element will be present. A subnavigation element will have a "Navigation Leaf" if it has a displayable navigation item.
You can then write functions/methods that will display your navigation based on the definition you choose.
What I would do, is something along these lines:
class MenuItem {
protected $active = false;
protected $children = array();
protected $name = '';
protected $link = '';
public function __construct($name, $link, $active) {}
public function __toString() {
//render this item
$out = ''; #render here
if (!$this->isActive()) {
return $out;
}
$out .= '<ul>';
foreach ($this->children as $child) {
$out .= (string) $child;
}
$out .= '</ul>';
return $out;
}
public function isActive() {
if ($this->active) {
return true;
}
foreach ($this->children as $child) {
if ($child->isActive()) {
return true;
}
}
return false;
}
}
Then, all you have is a collection of root menu items in an array... To build your menu, you just do:
$rootItems = array($item1, $item2);
$out = '<ul>';
foreach ($rootItems as $item) {
$out .= (string) $item;
}
$out .= '</ul>';
I'll leave the semantics of constructing the object, adding children, etc to the user...
What about rewrite nav function in the next way:
function nav($links, $level){
foreach($links as $k => $v) {
if (is_array($v)) {
nav($v, $level + 1)
} else {
echo buildLinks($k, $v, $base);
}
}
}
And than call it:
$links = array(
array(
"Home" => "home.php",
"submenu1" => array(
"something1"=>"something1.php",
"something2" => "something2.php")
),
array("Pics" => "pics.php"),
array("About" => "about.php")
);
nav($links, 0);
Simplest way, IMHO, is to just make a recursive call, and use a tree structured description of your navigation (that is, nested arrays). Untested example code:
<?php
$links = array(
"Home" => array("home.php", array(
"something1"=> array("something1.php", array()),
"hello"=> array("hello.php", array(
"world" => array("world.php", array()),
"bar" => array("bar.php", array()),
)),
)),
"Pics" => array("pics.php", array(
"people"=>"people.php",
"places" => "places.php",
)),
"About" => array("about.php", array()), // example no subitems
);
// use the following $path variable to indicate the current navigational position
$path = array(); // expand nothing
$path = array('Home'); // expand Home
$path = array('Home', 'hello'); // also expand hello in Home
// map indent levels to classes
$classes = array(
'item',
'subitem',
'subsubitem',
);
// recursive function to build navigation list
function buildNav($links, $path, $classes)
{
// selected page at current level
// NOTE: array_shift returns NULL if $path is empty.
// it also alters the array itself
$selected = array_shift($path);
$class = array_shift($classes);
echo "<ul>\n";
foreach($links as $name => $link)
{
list($href, $sublinks) = $link;
if ($name == $selected)
{
echo "<li class=\"selected $class\">$name\n";
// recursively show subitems
// NOTE: path starts now with the selected subitem
buildNav($sublinks, $path, $classes);
echo "</li>\n";
}
else
{
echo "<li>$name</li>\n";
}
}
echo "<ul>\n";
}
// actually build the navigation
buildNav($links, $path, $classes);
?>
#catchmeifyoutry
Thank you, you saved my life LoL.
I changed your function a little to adapt it to my use and this came out:
$html['navi'] = array(
"Home" => "/home/",
"DJs & Shows" => "/djs-shows/",
"Playlists" => "/playlists/",
"Newsbeat" => "/newsbeat/",
"Reviews" => "/reviews/",
"TV" => "/tv/",
"Contact" => "/contact/",
"Test" => array("/test/",
array("Submenu 1" => "/test/link1",
"Submenu 2" => "/test/link2",
"Submenu 3" => "/test/link3",
"Submenu 4" => "/test/link4",
"Submenu 5" => "/test/link5",
"Submenu 6" => "/test/link6"
)
)
);
$classes = array(
'first-level',
'second-level',
'third-level',
);
function siteNavi($links, $classes) {
// The best way for MultiArray navigation (LOVE IT!)
// Array Shift selects first element and removes it from array
$class = array_shift($classes);
echo "<ul class=\"$class\">\n";
foreach($links as $name => $link) {
if (is_array($link) AND $class != "") {
list($link, $sublinks) = $link;
if ($_GET['site'] == basename($link)) { $selected = ' class="current"'; } else { $selected = ""; }
echo "<li{$selected}>{$name}\n";
// recursively show subitems
// NOTE: path starts now with the selected subitem
siteNavi($sublinks, $classes);
echo "</li>\n";
} else {
if ($_GET['site'] == basename($link)) { $selected = ' class="current"'; } else { $selected = ""; }
echo "<li{$selected}><a href=\"{$link}\" >{$name}</a></li>\n";
}
}
echo "</ul>\n";
}
Thank you very much !
I wonder how much impact does have this kind of code on the page speed tho. Few microseconds of milliseconds :D

Categories