使用PHP';s迭代器


Nested lists using PHP's iterator?

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

$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"属性?这可以用对象而不是数组来实现吗?

您需要一个类迭代器吗?你只需要一个简单的函数就可以做到这一点。。。

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之外,每个阵列中是否还有更多的属性?

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

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

首先让我向您解释几件事。你的阵列有两个图案

  1. 带有数字索引的
  2. 一个具有字符串索引,具有不同解析的titlechildren

我认为递归函数在这方面起到了很好的作用,而不是复杂的逻辑。我们的递归函数必须能够分别处理这两种模式。

这是我的函数版本,你可以用它来解释

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

我会选择一个简单的递归函数,将数组扁平化为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);
}

使用输入的输出:

  • 节点级别1
  • 节点级别1
    • 节点级别2
    • 节点级别2
      • 节点级别3
      • 节点级别3
  • 节点级别1

或实际代码:

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

干杯