5

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;
}
Sonny
  • 8,204
  • 7
  • 63
  • 134
  • It should be possible somehow using `getChildren` or `nextElement`... But sadly, those classes are not much documented yet http://www.php.net/manual/en/class.recursiveiteratoriterator.php – Pekka Dec 03 '10 at 17:29
  • What mode is the `RecursiveIteratorIterator` running with (it's the second parameter to the constructor)? `LEAVES_ONLY`, `CHILD_FIRST` or `SELF_FIRST` (`LEAVES_ONLY` is the default)? – ircmaxell Dec 03 '10 at 17:41
  • can you give a usage example of how you intend to use it please – Gordon Dec 03 '10 at 20:16
  • I've updated my question to clarify, based on the the comments. – Sonny Dec 06 '10 at 15:26
  • Why did this become a community wiki? – Sonny Dec 07 '10 at 14:59

2 Answers2

3

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('.'))));
rik
  • 8,592
  • 1
  • 26
  • 21
  • You've posted some nice code examples here, but I'm not seeing where you determine the first or last item. – Sonny Dec 06 '10 at 15:31
  • @Sonny: In the first snippet `$rii->hasNext()` is true for all but the last element and `count($rii->getCache())` equals 1 if the iterator is on the first element. In the second snippet `reset($tree) !== $value && end($tree) !== $value` is true for all but the first and last elements. – rik Dec 06 '10 at 15:39
  • Based on your description, it sounds like it only determines the first and last element of the "flattened" iterator, and not the first and last element of the branch/level. To be fair, my original question may not have made that clear, so I've updated it to clarify. – Sonny Dec 06 '10 at 15:55
  • The recursive iterator is never "flattened" nor is the `$tree`. Please try the code snippets. I've made them work with a diretory structure because it's easy to test - everony has directories. ;) But you can replace `$rdi` with a different iterator or use your menu structure instead of calling `buildTree()`. – rik Dec 06 '10 at 16:08
  • I am trying to use your code. `hasNext()` is an "unknown method". – Sonny Dec 06 '10 at 16:41
  • It's PHP Version 5.2.13. I've updated my question with that information. The documentation makes no mention of `hasNext()` either: http://www.php.net/manual/en/class.recursiveiteratoriterator.php – Sonny Dec 06 '10 at 20:43
  • I found `hasNext()` in the `RecursiveCachingIterator` documentation and added it to my code. It works! Thanks so much. I'll award the bounty to you when SO allows me to. – Sonny Dec 06 '10 at 21:06
  • How is this working! hasNext() is a void method, it does not return anything!! – Broncha Apr 24 '17 at 10:47
0

If $iterator is a dense array, this might work:

// iterate container
$prevDepth = -1;
foreach ($iterator as $key => $page) {
    $depth = $iterator->getDepth();
    /* snip */
    if ($depth > $prevDepth) {
        // $page is first branch item

        if (isset($iterator[$key - 1])) {
            // $iterator[$key - 1] is last branch item in previous branch
        }
    }
    /* snip */
    $prevDepth = $depth;
}

You will have to test for the very last item separately.

Adrian Schmidt
  • 1,886
  • 22
  • 35