Nested Lists Using a PHP Iterator?

I am trying to display this 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 that takes into account the "children" property. So far I have 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 perfectly right: I get every element enclosed between <ul>, and I don't know how to close <li>s ...

Any ideas on how to make this work? Is it also possible to get all the properties of the array (the actual element), and not just the title property in my foreach () loop? And can this be done with objects instead of arrays?

+5
source share
5 answers

RecursiveCachingIterator, , . , (: 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 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><a href="' . $url . '">' . $name . '</a>';
        } else {
            $output .= '<li class="last"><a href="' . $url . '">' . $name . '</a>';
        }

        // 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;
    }

}
?>

<?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;
}
?>
+2

? ...

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;
}

:

<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>

, , , . , title children?

+14

, , foreach(), . , , .

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
?>
+4

, text/html:

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);
}

:

  • NodeLvl1
  • NodeLvl1
    • NodeLvl2
    • NodeLvl2
      • NodeLvl3
      • NodeLvl3
  • NodeLvl1

:

<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>

+3

.

  • , title children, -

, , . .

,

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;
  }
}

  • ,
  • PHP
+3

All Articles