嵌套列表使用PHP的迭代器?

时间:2012-02-28 01:10:53

标签: php arrays

我正在尝试显示这种数组:

$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(),
       ),    
);
像这样:

<ul>
  <li>
    NodeLvl1
  </li>
  <li>
    NodeLvl1
      <ul>
        <li>NodeLv2</li>
         ...

      </ul>
  </li>
  ...

基本上是一个考虑到“children”属性的嵌套列表。到目前为止,我已经想出了这个:

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;

哪种方式不正常:我将每个项目包裹在<ul>之间,我不知道如何关闭<li> ...

有关如何使这项工作的任何想法?也可以获取所有数组属性(实际元素),而不仅仅是我的foreach()循环中的“title”属性?这可以用对象而不是数组来完成吗?

5 个答案:

答案 0 :(得分:14)

你需要一个类迭代器吗?你可以用一个简单的函数来做到这一点......

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>

这涵盖了您向我们展示的内容,但我不确定您对其他属性的含义。除了titlechildren之外,每个数组中还有更多属性吗?

答案 1 :(得分:4)

不要试图像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
?>

答案 2 :(得分:3)

我会选择一个简单的递归函数,将数组展平为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 :(得分:3)

首先让我向你解释一些事情。你的数组有两个模板

  1. 一个带有数字索引
  2. 一个带有字符串索引,其中titlechildren的解析方式不同
  3. 我认为递归函数在这部分扮演着非常好的角色,而不是复杂逻辑。我们的递归函数必须能够分别处理这两种模式。

    这是我可以使用的函数版本

    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中简单逻辑的力量

答案 4 :(得分:2)

您可以使用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'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><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;
}
?>