Search code examples
phpzend-frameworkrecursionzend-navigation

PHP RecursiveIteratorIterator: Determining first and last item at each branch level


I have extended Zend_View_Helper_Navigation_Menu, and it uses a RecursiveIteratorIterator to iterate over the menu tree. What I want to be able to determine is whether I am on the first or last item for a branch level in the tree.

Here's an example of what I'm looking for:

  • Nav 1 (first)
    • Nav 1.1 (first & last)
      • Nav 1.1.1 (first)
      • Nav 1.1.2
      • Nav 1.1.3 (last)
  • Nav 2
    • Nav 2.1 (first)
    • Nav 2.2 (last)
  • Nav 3 (last)
    • Nav 3.1 (first)
    • Nav 3.2 (last)

Additional Information

  • PHP Version 5.2.13

Solution

Within the foreach ($iterator as $page) loop two variables can be used to keep track of the depths, $depth and $prevDepth. A simple comparison conditional can then determine the first item in a branch level: if ($depth > $prevDepth).

Creating a RecursiveCachingIterator using the Zend_Navigation_Container object and then using that to create the RecursiveIteratorIterator adds the the hasNext() method.

$rci = new RecursiveCachingIterator($container, CachingIterator::FULL_CACHE);
$iterator = new RecursiveIteratorIterator($rci,
                    RecursiveIteratorIterator::SELF_FIRST);
/* snip */
$prevDepth = -1;
foreach ($iterator as $page) {
    $depth = $iterator->getDepth();
    /* snip */
    if ($depth > $prevDepth) {
        // first branch item
    }
    /* snip */
    if (!$iterator->hasNext()) {
        // last branch item
    }
    /* snip */
    $prevDepth = $depth;
}

Solution

  • Using RecursiveCachingIterator:

    $rdi = new RecursiveDirectoryIterator('.');
    $rci = new RecursiveCachingIterator($rdi, CachingIterator::FULL_CACHE); 
    $rii = new RecursiveIteratorIterator($rci, RecursiveIteratorIterator::SELF_FIRST);
    
    foreach ($rii as $file) {
        if ($file->isDir()) {
            echo $file->getFilename() . PHP_EOL;
        }
        elseif (!$rii->hasNext()) {
            echo $file->getFilename() . PHP_EOL;
        }
        elseif (count($rii->getCache()) == 1) {
            echo $file->getFilename() . PHP_EOL;
        }
    }
    

    Another solution with array:

    function buildTree(RecursiveDirectoryIterator $iterator) {
        $tree = array();
        foreach ($iterator as $fileinfo) {
            if ($fileinfo->isDir()) {
                $tree[$fileinfo->getFilename()] = buildTree($iterator->getChildren());
            } else {
                $tree[$fileinfo->getFilename()] = $fileinfo->getFilename();
            }
        }
        return $tree;
    }
    
    function filterTree(array $tree) {
        foreach ($tree as $key => $value) {
            if (is_array($value)) {
                $tree[$key] = filterTree($value);
            } elseif (reset($tree) !== $value && end($tree) !== $value) {
                unset($tree[$key]);
            }
        }
        return $tree;
    }
    
    print_r(filterTree(buildTree(new RecursiveDirectoryIterator('.'))));