0

I found a couple of questions (this one and this question) related to the SPL iterators, but I'm not sure if they're helpful in my case, as I'm using a rather high level extension of the RecursiveIteratorIterator; the DirectoryTreeIterator.

Could somebody perhaps show me how to alter the DirectoryTreeIterator or how to sort the returned array per directory after it has been outputted by the iterator?

A method of sorting the files correctly directly on the Apache server is also an option for me, if it's possible using .htaccess for example.

This is the code of DirectoryTreeIterator from the SPL:

/** @file directorytreeiterator.inc
 * @ingroup Examples
 * @brief class DirectoryTreeIterator
 * @author  Marcus Boerger
 * @date    2003 - 2005
 *
 * SPL - Standard PHP Library
 */

/** @ingroup Examples
 * @brief   DirectoryIterator to generate ASCII graphic directory trees
 * @author  Marcus Boerger
 * @version 1.1
 */

class DirectoryTreeIterator extends RecursiveIteratorIterator
{
    /** Construct from a path.
     * @param $path directory to iterate
     */
    function __construct($path) {
        parent::__construct(
            new RecursiveCachingIterator(
                new RecursiveDirectoryIterator($path, RecursiveDirectoryIterator::KEY_AS_FILENAME
                ), 
                CachingIterator::CALL_TOSTRING|CachingIterator::CATCH_GET_CHILD
            ), 
            parent::SELF_FIRST
        );
    }

    /** @return the current element prefixed with ASCII graphics
     */ 
    function current() {
        $tree = '';
        for ($l=0; $l < $this->getDepth(); $l++) {
            $tree .= $this->getSubIterator($l)->hasNext() ? ' ' : ' ';
        }
        return $tree . ($this->getSubIterator($l)->hasNext() ? ' ' : ' ') 
               . $this->getSubIterator($l)->__toString();
    }

    /** Aggregates the inner iterator
     */ 
    function __call($func, $params) {
        return call_user_func_array(array($this->getSubIterator(), $func), $params);;
    }
}

To clarify why I'm using the code above is because it fits my needs exactly. I want to generate a recursive directory tree prefixed by whitespaces - the original code example by Marcus Boerger adds some ASCI elements. The problem is I don't have control over the sorting of files and directories, so I would like the directory tree to appear like this:

dir001
  subdir001
    subdir002
      subfile001.jpg
  file001.png
  file002.png
  file003.png
dir002
  apple.txt
  bear.txt
  contact.txt
dir003
[...]

Instead, the listings returned by the iterator isn't sorted at all and it shows me something like this:

dir002
  bear.txt
  apple.txt
  contact.txt
dir001
  subdir001
    subdir002
      subfile001.jpg
  file002.png
  file001.png
  file003.png
dir003
[...]

So I guess the solution I'm looking for is some way to call a sort method every time a subdirectory is indexed and added to the directory tree.

I hope I've made it somewhat clearer, as a nonnative speaker it's sometimes hard to put thoughts into coherent sentences (or even words for that matter).

Community
  • 1
  • 1
mensch
  • 4,411
  • 3
  • 28
  • 49

2 Answers2

3

Well, I'm not sure where you got that class from, but it's doing some pretty messed up things (including a few bugs to say the least). And while it uses SPL, it's not an SPL class.

Now, I'm not 100% sure what you mean by "sort", but assuming you're talking about a natural sort, why not just flatten an array, and then sort it?

$it = new RecursiveTreeIterator(
    new RecrusiveDirectoryIterator($dir),
    RecursiveTreeIterator::BYPASS_KEY,
    CachingIterator::CALL_TOSTRING
);
$files = iterator_to_array($it);
natsort($files);
echo implode("\n", $files);

or

$it = new RecursiveIteratorIterator(
    new RecursiveDirectoryIterator($dir),
    RecursiveIteratorIterator::SELF_FIRST
);
$files = iterator_to_array($it);
$files = array_map(function($file) { return (string) $file; }, $files);
natsort($files);
echo implode("\n", $files);

Edit: Based on your edit, here's how I would solve it:

function BuildTree($it, $separator = '  ', $level = '') {
    $results = array();
    foreach ($it as $file) {
        if (in_array($file->getBasename(), array('.', '..'))) {
            continue;
        }
        $tmp = $level . $file->getBaseName();
        if ($it->hasChildren()) {
            $newLevel = $level . $separator;
            $tmp .= "\n" . BuildTree($it->getChildren(), $separator, $newLevel);
        }
        $results[] = $tmp;
    }
    natsort($results);
    return implode("\n", $results);
};
$it = new RecursiveDirectoryIterator($dir);
$tree = BuildTree($it);

It's a pretty simple recursive parser, and does the natural sort on each level.

ircmaxell
  • 163,128
  • 34
  • 264
  • 314
  • I think you're right about the iterator not being part of SPL, but it's listed in the examples: http://www.php.net/~helly/php/ext/spl/dir_e16bad08fe0100cb7c8253fd4ebce86a.html and here: http://www.php.net/~helly/php/ext/spl/directorytreeiterator_8inc.html – mensch Mar 24 '11 at 10:59
  • @mensch Well, that's just an example usage. Not to mention that it appears that you've modified it (hence introducing the bugs I was talking about). If you give an example of what you're after, perhaps we can better help... – ircmaxell Mar 24 '11 at 11:01
  • You're right I altered the code a bit so that it outputs whitespaces instead of the ASCII characters. At first glance, I can't spot any other differences between the original example and my version, though. The reason why I choose to incorporate the example is because I want to generate the directorytree prefixed with whitespaces. I'll update my question with a bit of extra information as to what I'm trying to accomplish. – mensch Mar 24 '11 at 11:13
  • Thanks a million! Always have a hard time wrapping my head around recursion. I've just replaced the old code with your BuildTree method and it's working well now. I replaced $file->getBaseName() with $file->getPathname() because I need the full path to the directories. – mensch Mar 24 '11 at 14:08
0

Don't know about SPL iterators, but for your iterator you should put the items in an array, then sort them and add them to $tree. I modified the function current but didn't test it:

function current()
{
    $tree = '';

    $treeitems = array();
    for ($l=0; $l < $this->getDepth(); $l++) {
        //NOTE: On this line I think you have an error in your original code:
        //      This ? ' ' : ' ' is strange
        $treeitems[] = $this->getSubIterator($l)->hasNext() ? ' ' : ' ';
    }
    $treeitems.sort();
    for each ($treeitems as $treeitem)
        $tree .= $treeitem;

    return $tree . ($this->getSubIterator($l)->hasNext() ? ' ' : ' ') 
           . $this->getSubIterator($l)->__toString();
}
BenMorel
  • 34,448
  • 50
  • 182
  • 322
Hossein
  • 4,097
  • 2
  • 24
  • 46