Recursive Directory Tree

Small, short code snippets that other people may find useful. Do you have a good regex that you would like to share? Share it! Even better, the code can be commented on, and improved.

Moderator: General Moderators

User avatar
s.dot
Tranquility In Moderation
Posts: 5001
Joined: Sun Feb 06, 2005 7:18 pm
Location: Indiana

Recursive Directory Tree

Post by s.dot »

I noticed a couple people looking for directory trees lately. I came up with this function. The formatting gets messed up if it goes more than one level deep; but it does list the directories. :) I tried to get the formatting right but I couldn't get my brain around it.

Code: Select all

<?php
 
function dirList($directory,$padding=20,$level=1){
    $handle = opendir($directory);
    $ret = '';
    
    while (false !== ($file = readdir($handle))) {
        if(($file != '.') && ($file != '..')){
            if(is_dir($file)){
                $ret .= $file.'<p style="padding-left: '.$padding*$level.'px;">'.dirList($file).'</p>';
            } else {
                $ret .= $file.'<br />';
            }
        }
    }
    closedir($handle);
    return $ret;
}
 
echo dirList('./');
 
?>
Last edited by Weirdan on Sat May 03, 2008 7:31 am, edited 1 time in total.
Reason: php tags
Set Search Time - A google chrome extension. When you search only results from the past year (or set time period) are displayed. Helps tremendously when using new technologies to avoid outdated results.
User avatar
feyd
Neighborhood Spidermoddy
Posts: 31559
Joined: Mon Mar 29, 2004 3:24 pm
Location: Bothell, Washington, USA

Post by feyd »

PHP 5.1.2 tested

Code: Select all

function DirTree(RecursiveDirectoryIterator $dir)
{
    $tree = array();
    $dirs = array(array($dir, &$tree));
    
    for($i = 0; $i < count($dirs); ++$i)
    {
        $d =& $dirs[$i][0];
        $tier =& $dirs[$i][1];
 
        for($d->rewind(); $d->valid(); $d->next())
        {
            if ($d->isDir())
            {
                $tier[$d->getFilename()] = array();
                $dirs[] = array($d->getChildren(), &$tier[$d->getFilename()]);
            }
            else
            {
                $tier[$d->getFilename()] = $d->getSize();
            }
        }
    }
    
    return $tree;
}
 
$dir = new RecursiveDirectoryIterator(getcwd());
$d = DirTree($dir);
var_export($d);
outputs (for SimpleTest's directory tree)

Code: Select all

array (
  'authentication.php' => 8332,
  'browser.php' => 38659,
  'collector.php' => 3464,
  'compatibility.php' => 6368,
  'cookies.php' => 13314,
  'detached.php' => 3169,
  'docs' =>
  array (
    'en' =>
    array (
      'authentication_documentation.html' => 12120,
      'browser_documentation.html' => 16006,
      'docs.css' => 1414,
      'expectation_documentation.html' => 13136,
      'form_testing_documentation.html' => 10478,
      'group_test_documentation.html' => 12240,
      'index.html' => 17681,
      'mock_objects_documentation.html' => 30541,
      'overview.html' => 18287,
      'partial_mocks_documentation.html' => 14570,
      'reporter_documentation.html' => 20732,
      'unit_test_documentation.html' => 14718,
      'web_tester_documentation.html' => 20339,
    ),
    'fr' =>
    array (
      'authentication_documentation.html' => 11429,
      'browser_documentation.html' => 17051,
      'docs.css' => 1414,
      'expectation_documentation.html' => 13822,
      'form_testing_documentation.html' => 10905,
      'group_test_documentation.html' => 12949,
      'index.html' => 18552,
      'mock_objects_documentation.html' => 31247,
      'overview.html' => 20230,
      'partial_mocks_documentation.html' => 16125,
      'reporter_documentation.html' => 21414,
      'server_stubs_documentation.html' => 15020,
      'unit_test_documentation.html' => 15243,
      'web_tester_documentation.html' => 21006,
    ),
  ),
  'dumper.php' => 16199,
  'encoding.php' => 15815,
  'errors.php' => 6215,
  'exceptions.php' => 1339,
  'expectation.php' => 25300,
  'extensions' =>
  array (
    'pear_test_case.php' => 6931,
    'phpunit_test_case.php' => 3137,
  ),
  'form.php' => 13003,
  'frames.php' => 20687,
  'HELP_MY_TESTS_DONT_WORK_ANYMORE' => 11726,
  'http.php' => 21694,
  'invoker.php' => 4079,
  'LICENSE' => 26421,
  'mock_objects.php' => 51679,
  'page.php' => 32449,
  'parser.php' => 28769,
  'README' => 4553,
  'reflection_php4.php' => 3326,
  'reflection_php5.php' => 9779,
  'remote.php' => 3855,
  'reporter.php' => 13256,
  'scorer.php' => 25079,
  'selector.php' => 3638,
  'shell_tester.php' => 10642,
  'simpletest.php' => 8930,
  'socket.php' => 7076,
  'tag.php' => 43475,
  'test' =>
  array (
    'acceptance_test.php' => 65863,
    'adapter_test.php' => 2129,
    'all_tests.php' => 551,
    'authentication_test.php' => 6192,
    'browser_test.php' => 37297,
    'collector_test.php' => 1788,
    'compatibility_test.php' => 3350,
    'cookies_test.php' => 10366,
    'detached_test.php' => 519,
    'dumper_test.php' => 3317,
    'encoding_test.php' => 7579,
    'errors_test.php' => 4443,
    'expectation_test.php' => 11560,
    'form_test.php' => 16619,
    'frames_test.php' => 22966,
    'http_test.php' => 20773,
    'interfaces_test.php' => 3142,
    'live_test.php' => 2015,
    'mock_objects_test.php' => 25165,
    'page_test.php' => 39037,
    'parser_test.php' => 24026,
    'parse_error_test.php' => 303,
    'reflection_php4_test.php' => 2029,
    'reflection_php5_test.php' => 5558,
    'remote_test.php' => 840,
    'shell_test.php' => 1285,
    'shell_tester_test.php' => 1455,
    'simpletest_test.php' => 416,
    'socket_test.php' => 827,
    'support' =>
    array (
      'collector' =>
      array (
        'collectable.1' => 0,
        'collectable.2' => 0,
      ),
      'latin1_sample' => 18,
      'spl_examples.php' => 401,
      'supplementary_upload_sample.txt' => 22,
      'upload_sample.txt' => 30,
    ),
    'tag_test.php' => 23566,
    'test_groups.php' => 3226,
    'test_with_parse_error.php' => 175,
    'unit_tester_test.php' => 1982,
    'unit_tests.php' => 484,
    'url_test.php' => 19210,
    'user_agent_test.php' => 15946,
    'visual_test.php' => 14621,
    'web_tester_test.php' => 7309,
    'xml_test.php' => 7675,
  ),
  'test_case.php' => 24683,
  'unit_tester.php' => 14777,
  'url.php' => 18608,
  'user_agent.php' => 12717,
  'VERSION' => 11,
  'web_tester.php' => 56837,
  'xml.php' => 20576,
)
Last edited by Weirdan on Sat May 03, 2008 7:31 am, edited 1 time in total.
Reason: php tags
User avatar
feyd
Neighborhood Spidermoddy
Posts: 31559
Joined: Mon Mar 29, 2004 3:24 pm
Location: Bothell, Washington, USA

Post by feyd »

A version of the above compatible with PHP 4 producing the exact same output.

Code: Select all

function DirTree4($dir)
{
    $tree = array();
    $dirs = array(array($dir, &$tree));
    
    for($i = 0; $i < count($dirs); ++$i)
    {
        $d = opendir($dirs[$i][0]);
        $tier =& $dirs[$i][1];
 
        while($file = readdir($d))
        {
            if ($file != '.' and $file != '..')
            {
                $path = $dirs[$i][0] . DIRECTORY_SEPARATOR . $file;
                if (is_dir($path))
                {
                    $tier[$file] = array();
                    $dirs[] = array($path, &$tier[$file]);
                }
                else
                {
                    $tier[$file] = filesize($path);
                }
            }
        }
    }
    
    return $tree;
}
 
$d = DirTree4(getcwd());
var_export($d);
Last edited by Weirdan on Sat May 03, 2008 7:32 am, edited 1 time in total.
Reason: php tags
User avatar
s.dot
Tranquility In Moderation
Posts: 5001
Joined: Sun Feb 06, 2005 7:18 pm
Location: Indiana

Post by s.dot »

Geez feyd owned me publicly :(

Just kidding. Those are very nice functions though.
Set Search Time - A google chrome extension. When you search only results from the past year (or set time period) are displayed. Helps tremendously when using new technologies to avoid outdated results.
User avatar
feyd
Neighborhood Spidermoddy
Posts: 31559
Joined: Mon Mar 29, 2004 3:24 pm
Location: Bothell, Washington, USA

Post by feyd »

The nice part: no recursion neccessary!
cappaberra
Forum Newbie
Posts: 1
Joined: Sat Mar 03, 2007 1:42 pm

Post by cappaberra »

feyd,

I think your iterative solution is very elegant! It utilizes a great use of references. Very cool. :-) I'm going to use the general algorithm if you don't mind... I'll make sure I give you credit.

It took me about 50 times looking over it to understand how the references work, but once I started getting it, I really liked the way you did things.

Take care,
cappaberra
User avatar
feyd
Neighborhood Spidermoddy
Posts: 31559
Joined: Mon Mar 29, 2004 3:24 pm
Location: Bothell, Washington, USA

Post by feyd »

cappaberra wrote:feyd,

I think your iterative solution is very elegant! It utilizes a great use of references. Very cool. :-) I'm going to use the general algorithm if you don't mind... I'll make sure I give you credit.

It took me about 50 times looking over it to understand how the references work, but once I started getting it, I really liked the way you did things.

Take care,
cappaberra
I'm glad you enjoyed it. By all means, please do use it. I don't really care for the credit, but I'll take it. :)

My obsession with unraveling recursive structures stems from my days in C where the stack could very easily overflow quickly when recursing. Unwinding them takes a bit of creativity, I've yet to really find one I couldn't convert.

:)
User avatar
Ambush Commander
DevNet Master
Posts: 3698
Joined: Mon Oct 25, 2004 9:29 pm
Location: New Jersey, US

Post by Ambush Commander »

Ooh, that's great. Definitely will be using this in my project.
User avatar
Jenk
DevNet Master
Posts: 3587
Joined: Mon Sep 19, 2005 6:24 am
Location: London

Post by Jenk »

I just spent 5mins oogling at that code, puzzled by how it will reach further than one level, finally realising that the $dirs array is updated by reference. Nice :) I will bear that practice in mind when I need to recursively search again in future. :)
godwulf
Forum Newbie
Posts: 1
Joined: Thu Apr 26, 2007 5:28 pm

Excerpt from PHP.net examples

Post by godwulf »

Just one small correction to the above code, to avoid directories named "0" from being considered the end of the directory.

Code: Select all

/* This is the correct way to loop over the directory. */
while (false !== ($file = readdir($handle))) {
    echo "$file\n";
}
 
/* This is the WRONG way to loop over the directory. */
while ($file = readdir($handle)) {
    echo "$file\n";
}
streamkid
Forum Newbie
Posts: 3
Joined: Wed May 23, 2007 7:12 am

Post by streamkid »

hello, my first post :D

i was looking for a directory tree to use it online, but i didn't find any implementation that would also print a link to the file
so i made one :)

you can see it here:
http://www.streamkid.de/~alex/?cat=stuff

Code: Select all

 
/* you call the function like this: 
$img = array ('images');
ldir('images/', 0, 0, $img, 'images');
this will print the contents of directory images */
        function ldir($path, $level, $m, $struct, $abs) {
                $ignore = array ('cgi-bin', '.', '..', '.htaccess', '.t3hdirectives');
                $dh = @opendir($path);
                while (false !== ($file = readdir($dh))) {
                        if (!in_array($file, $ignore)) {
                                $spaces = str_repeat('&nbsp;', ($level*4));
                                $struct[$level] = $abs;
                                $url = '';
                                if ($level > 0) {
                                        for ($i = 0; $i <= $level; $i += 1) {
                                                $url .= $struct[$i];
                                                $url .= '/';
                                            }
                                        $url .= $file;
                                    }
                                else {
                                        $url .= $abs;
                                        $url .= '/';
                                        $url .= $file;
                                    }
                                if (is_dir("$path/$file")) {
                                        echo "<a title=\"This is a directory. Nothing to link to.\">$spaces $file</a>&nbsp;<i>(dir)</i><br/>";
                                        $dir .= '/';
                                        $dir .= $file;
                                        ldir ("$path/$file", ($level + 1), $m, $struct, $file);
                                    } 
                                else {
                                        switch ($m) {
                                                case 0: echo "<a href=\"?cat=stuff&img=$url\">$spaces $file</a><br/>"; break;
                                                case 1: echo "<a href=\"$url\">$spaces $file</a><br/>";             break;
                                                case 2: echo "<a href=\"$url\">$spaces $file</a><br/>";                break;
                                            }
                                    }
                            }
                    }
                closedir($dh);
            }
/* you can completely delete $m and the switch statement, i just used it because depending on the type the url differs */
 
i just started learning php, so your comments on the code are more than welcome :)
Last edited by Weirdan on Sat May 03, 2008 7:34 am, edited 1 time in total.
Reason: php tags
User avatar
onion2k
Jedi Mod
Posts: 5263
Joined: Tue Dec 21, 2004 5:03 pm
Location: usrlab.com

Post by onion2k »

2 comments:

1. Never use the @ symbol to suppress errors. Write your code so that it evaluates the return from a function call and acts accordingly.

2. Personally I'd fetch the directory structure into an array and then pass that to a function (or an object) that builds an HTML view of it with the necessary links. Separating the logic of scanning a file system and displaying the HTML will lead to a more maintainable project in the long term.
streamkid
Forum Newbie
Posts: 3
Joined: Wed May 23, 2007 7:12 am

Post by streamkid »

Thanks for commenting:)
About (2): Perhaps I will do that on v2 ;p
I guess its more difficult to be coded, so I want to gain some more expierence first and then refactor the thing :)
Begby
Forum Regular
Posts: 575
Joined: Wed Dec 13, 2006 10:28 am

Post by Begby »

I love SPL

This snippet will recursively list all the folders and files in the specified path.

Code: Select all

 
$it = new RecursiveDirectoryIterator('/path/to/list');
foreach(new RecursiveIteratorIterator($it, RecursiveIteratorIterator::SELF_FIRST) as $file)
{
    echo $file ;        
}
 

$file is an splFileInfo object that uses __toString() for the echo above. You can also use the following methods.

Code: Select all

 
        echo "File name = " . $file->getFilename() . "\n"; 
        echo "Path name = " . $file->getPathname() . "\n"; 
        echo "Permission = " . $file->getPerms() . "\n"; 
        echo "Inod = " . $file->getInode() . "\n"; 
        echo "Size = " . $file->getSize() . "\n"; 
        echo "Owner = " . $file->getOwner() . "\n"; 
        echo "Group = " . $file->getGroup() . "\n"; 
        echo "Atime = " . $file->getATime() . "\n"; 
        echo "Mtime = " . $file->getMTime() . "\n"; 
        echo "CTime = " . $file->getCTime() . "\n"; 
        echo "Type = " . $file->getType() . "\n"; 
        echo "Writable = " . $file->isWritable() . "\n"; 
        echo "Readable = " . $file->isReadable() . "\n"; 
        echo "Executable = " . $file->isExecutable() . "\n"; 
        echo "Is file = " . $file->isFile() . "\n"; 
        echo "Is directory = " . $file->isDir() . "\n"; 
        echo "Is link = " . $file->isLink() . "\n"; 
        echo "Is dot = " . $file->isDot() . "\n"; 
        echo "To string = " . $file->__toString() . "\n"; 
 
To create an indented list you could explode the path on the directory separator then subtract from the the number depth of your search path. That would be how far to indent your files.
Last edited by Weirdan on Sat May 03, 2008 7:34 am, edited 1 time in total.
Reason: php tags
dream2rule
Forum Contributor
Posts: 109
Joined: Wed Jun 13, 2007 5:07 am

Post by dream2rule »

feyd wrote:PHP 5.1.2 tested

Code: Select all

function DirTree(RecursiveDirectoryIterator $dir)
{
    $tree = array();
    $dirs = array(array($dir, &$tree));
    
    for($i = 0; $i < count($dirs); ++$i)
    {
        $d =& $dirs[$i][0];
        $tier =& $dirs[$i][1];
 
        for($d->rewind(); $d->valid(); $d->next())
        {
            if ($d->isDir())
            {
                $tier[$d->getFilename()] = array();
                $dirs[] = array($d->getChildren(), &$tier[$d->getFilename()]);
            }
            else
            {
                $tier[$d->getFilename()] = $d->getSize();
            }
        }
    }
    
    return $tree;
}
 
$dir = new RecursiveDirectoryIterator(getcwd());
$d = DirTree($dir);
var_export($d);
outputs (for SimpleTest's directory tree)

Code: Select all

array (
  'authentication.php' => 8332,
  'browser.php' => 38659,
  'collector.php' => 3464,
  'compatibility.php' => 6368,
  'cookies.php' => 13314,
  'detached.php' => 3169,
  'docs' =>
  array (
    'en' =>
    array (
      'authentication_documentation.html' => 12120,
      'browser_documentation.html' => 16006,
      'docs.css' => 1414,
      'expectation_documentation.html' => 13136,
      'form_testing_documentation.html' => 10478,
      'group_test_documentation.html' => 12240,
      'index.html' => 17681,
      'mock_objects_documentation.html' => 30541,
      'overview.html' => 18287,
      'partial_mocks_documentation.html' => 14570,
      'reporter_documentation.html' => 20732,
      'unit_test_documentation.html' => 14718,
      'web_tester_documentation.html' => 20339,
    ),
    'fr' =>
    array (
      'authentication_documentation.html' => 11429,
      'browser_documentation.html' => 17051,
      'docs.css' => 1414,
      'expectation_documentation.html' => 13822,
      'form_testing_documentation.html' => 10905,
      'group_test_documentation.html' => 12949,
      'index.html' => 18552,
      'mock_objects_documentation.html' => 31247,
      'overview.html' => 20230,
      'partial_mocks_documentation.html' => 16125,
      'reporter_documentation.html' => 21414,
      'server_stubs_documentation.html' => 15020,
      'unit_test_documentation.html' => 15243,
      'web_tester_documentation.html' => 21006,
    ),
  ),
  'dumper.php' => 16199,
  'encoding.php' => 15815,
  'errors.php' => 6215,
  'exceptions.php' => 1339,
  'expectation.php' => 25300,
  'extensions' =>
  array (
    'pear_test_case.php' => 6931,
    'phpunit_test_case.php' => 3137,
  ),
  'form.php' => 13003,
  'frames.php' => 20687,
  'HELP_MY_TESTS_DONT_WORK_ANYMORE' => 11726,
  'http.php' => 21694,
  'invoker.php' => 4079,
  'LICENSE' => 26421,
  'mock_objects.php' => 51679,
  'page.php' => 32449,
  'parser.php' => 28769,
  'README' => 4553,
  'reflection_php4.php' => 3326,
  'reflection_php5.php' => 9779,
  'remote.php' => 3855,
  'reporter.php' => 13256,
  'scorer.php' => 25079,
  'selector.php' => 3638,
  'shell_tester.php' => 10642,
  'simpletest.php' => 8930,
  'socket.php' => 7076,
  'tag.php' => 43475,
  'test' =>
  array (
    'acceptance_test.php' => 65863,
    'adapter_test.php' => 2129,
    'all_tests.php' => 551,
    'authentication_test.php' => 6192,
    'browser_test.php' => 37297,
    'collector_test.php' => 1788,
    'compatibility_test.php' => 3350,
    'cookies_test.php' => 10366,
    'detached_test.php' => 519,
    'dumper_test.php' => 3317,
    'encoding_test.php' => 7579,
    'errors_test.php' => 4443,
    'expectation_test.php' => 11560,
    'form_test.php' => 16619,
    'frames_test.php' => 22966,
    'http_test.php' => 20773,
    'interfaces_test.php' => 3142,
    'live_test.php' => 2015,
    'mock_objects_test.php' => 25165,
    'page_test.php' => 39037,
    'parser_test.php' => 24026,
    'parse_error_test.php' => 303,
    'reflection_php4_test.php' => 2029,
    'reflection_php5_test.php' => 5558,
    'remote_test.php' => 840,
    'shell_test.php' => 1285,
    'shell_tester_test.php' => 1455,
    'simpletest_test.php' => 416,
    'socket_test.php' => 827,
    'support' =>
    array (
      'collector' =>
      array (
        'collectable.1' => 0,
        'collectable.2' => 0,
      ),
      'latin1_sample' => 18,
      'spl_examples.php' => 401,
      'supplementary_upload_sample.txt' => 22,
      'upload_sample.txt' => 30,
    ),
    'tag_test.php' => 23566,
    'test_groups.php' => 3226,
    'test_with_parse_error.php' => 175,
    'unit_tester_test.php' => 1982,
    'unit_tests.php' => 484,
    'url_test.php' => 19210,
    'user_agent_test.php' => 15946,
    'visual_test.php' => 14621,
    'web_tester_test.php' => 7309,
    'xml_test.php' => 7675,
  ),
  'test_case.php' => 24683,
  'unit_tester.php' => 14777,
  'url.php' => 18608,
  'user_agent.php' => 12717,
  'VERSION' => 11,
  'web_tester.php' => 56837,
  'xml.php' => 20576,
)



Hey really a nice script Feyd... now how do we convert this PHP code into HTML i.e, how are we supposed to change the raw code in a tree like HTML structure?
Last edited by Weirdan on Sat May 03, 2008 7:35 am, edited 1 time in total.
Reason: php tags
Post Reply