(PHP 4, PHP 5)
dir — Gibt eine Instanz der Verzeichnis-Klasse zurück
Ein pseudo-objektorientierter Mechanismus zum Lesen eines Verzeichnisses. Das in directory angegebene Verzeichnis wird geöffnet. Sobald das Verzeichnis geöffnet ist, stehen 2 Eigenschaften zur Verfügung. Die Eigenschaft Handle kann mit anderen Verzeichnis-Funktionen wie readdir(), rewinddir() und closedir() benutzt werden. Die Eigenschaft Path enthält die Pfadangabe des geöffneten Verzeichnisses. Es sind 3 Methoden verfügbar: read (lesen), rewind (zurücksetzen) und close (schließen).
Beispiel #1 dir() Beispiel
Beachten Sie im nachfolgenden Beispiel, wie der Rückgabewert der Funktion dir::read() überprüft wird. Es wird explizit getestet, ob der Rückgabewert identisch (den gleichen Wert und Datentyp hat, siehe Vergleichs-0peratoren) mit FALSE ist, weil sonst jeder Verzeichniseintrag, der zu FALSE ausgewertet wird, die Schleife abbricht.
<?php
$d = dir("/etc/php5");
echo "Handle: " . $d->handle . "\n";
echo "Path: " . $d->path . "\n";
while (false !== ($entry = $d->read())) {
   echo $entry."\n";
}
$d->close();
?>
Das oben gezeigte Beispiel erzeugt eine ähnliche Ausgabe wie:
Handle: Resource id #2 Path: /etc/php5 . .. apache cgi cli
Hinweis:
Die Reihenfolge der Verzeichniseinträge, die von der read-Methode zurückgegeben werden, ist systemabhängig.
When creating custom solutions, use predefined PHP constants to shorten your code and improve performances:
http://www.php.net/manual/en/reserved.constants.php
For example, DIRECTORY_SEPARATOR may replace a function in which you check PHP_OS to set if the directory separator is "/" or "\\".
Cheers.
That's the way, I'm storing recursive dirs to an array.
<?php
public static function getTreeFolders($sRootPath = UPLOAD_PATH_PROJECT, $iDepth = 0) {
      $iDepth++;
      $aDirs = array();
      $oDir = dir($sRootPath);
      while(($sDir = $oDir->read()) !== false) {
        if($sDir != '.' && $sDir != '..' && is_dir($sRootPath.$sDir)) {
          $aDirs[$iDepth]['sName'][] = $sDir;
          $aDirs[$iDepth]['aSub'][]  = self::getTreeFolders($sRootPath.$sDir.'/',$iDepth);
        }
      }
      $oDir->close();
      return empty($aDirs) ? false : $aDirs;
}
?>
Here my solution how to do effective recursiv directory listing.
Have fun.
<?php
/**
 * example of use: 
 */
$d = new RecDir("/etc/",false);
echo "Path: " . $d->getRootPath() . "\n";
while (false !== ($entry = $d->read())) {
   echo $entry."\n";
}
$d->close();
class RecDir
{
   protected $currentPath;
   protected $slash;
   protected $rootPath;
   protected $recursiveTree;   
   function __construct($rootPath,$win=false)
   {
      switch($win)
      {
         case true:
            $this->slash = '\\';
            break;
         default:
            $this->slash = '/';
      }
      $this->rootPath = $rootPath;
      $this->currentPath = $rootPath;
      $this->recursiveTree = array(dir($this->rootPath));
      $this->rewind();
   }
   function __destruct()
   {
      $this->close();
   }
   public function close()
   {
      while(true === ($d = array_pop($this->recursiveTree)))
      {
         $d->close();
      }
   }
   public function closeChildren()
   {
      while(count($this->recursiveTree)>1 && false !== ($d = array_pop($this->recursiveTree)))
      {
         $d->close();
         return true;
      }
      return false;
   }
   public function getRootPath()
   {
      if(isset($this->rootPath))
      {
         return $this->rootPath;
      }
      return false;
   }
   public function getCurrentPath()
   {
      if(isset($this->currentPath))
      {
         return $this->currentPath;
      }
      return false;
   }
   
   public function read()
   {
      while(count($this->recursiveTree)>0)
      {
         $d = end($this->recursiveTree);
         if((false !== ($entry = $d->read())))
         {
            if($entry!='.' && $entry!='..')
            {
               $path = $d->path.$entry;
               
               if(is_file($path))
               {
                  return $path;
               }
               elseif(is_dir($path.$this->slash))
               {
                  $this->currentPath = $path.$this->slash;
                  if($child = @dir($path.$this->slash))
                  {
                     $this->recursiveTree[] = $child;
                  }
               }
            }
         }
         else
         {
            array_pop($this->recursiveTree)->close();
         }
      }
      return false;
   }
   public function rewind()
   {
      $this->closeChildren();
      $this->rewindCurrent();
   }
   public function rewindCurrent()
   {
      return end($this->recursiveTree)->rewind();
   }
}
?>
Unlike the others here I've created a function that returns the directory's in a 2-dimensional array. Starting from the requested dir:
<?php
    /*
        getDirTree(string $dir [, bool $showfiles]);
        $dir of the folder you want to list, be sure to have an ending /
        $showfiles set to 'false' if files shouldnt be listed in the output array
    */
    function getDirTree($dir,$p=true) {
        $d = dir($dir);$x=array();
        while (false !== ($r = $d->read())) {
            if($r!="."&&$r!=".."&&(($p==false&&is_dir($dir.$r))||$p==true)) {
                $x[$r] = (is_dir($dir.$r)?array():(is_file($dir.$r)?true:false));
            }
        }
        foreach ($x as $key => $value) {
            if (is_dir($dir.$key."/")) {
                $x[$key] = getDirTree($dir.$key."/",$p);
            }
        }
        ksort($x);
        return $x;
    }
?>
Example Output:
Array
(
    [folder1] => Array
        (
            [subfolder1] => Array()
            [subfolder2] => Array()
            [text.txt] => 1
        )
    [folder2] => Array()
    [folder3] => Array
        (
            [text.txt] => 1
        )
)
The dir Class, from what I can tell, on a Windows box is not a  live image of the directory.  When the class is instantiated it takes a snapshot of the directory and then the iterator works off that.
I may be wrong, but when I run two processes that look to see if a directory exists, and then deletes the dir when some processing takes place.  Deletes from one process do not effect the iteration of the second.
To get around this I check that the file exists before doing my processing:
$d = dir($dataDir);
while (false !== ($entry = $d->read()))
if ($entry != '..' && $entry != '.' && file_exists("$dataDir\\$entry"))
{
    // do stuff
}
$d->close();
I run this as a batch process and can activate it multiple times to process the directory listing in parallel.
-CF
With SPL, you could recursively list all of the folders inside the current directory like this:
<?php
$it = new RecursiveDirectoryIterator('./');
// RecursiveIteratorIterator accepts the following modes:
//     LEAVES_ONLY = 0  (default)
//     SELF_FIRST  = 1
//     CHILD_FIRST = 2
foreach (new RecursiveIteratorIterator($it, 2) as $path) {
    if ($path->isDir()) {
        echo "$path\n";
    }
}
?>
<?php
 
  $i = new RecursiveIteratorIterator(new RecursiveDirectoryIterator('.'));
?>
works for me..
IMHO, thats take most effect with smaller number of errors;)
 
     function get_leaf_dirs($dir) 
      {
         $array = array();
         $d = @dir($dir);
         if($d)
         {
               while (false !== ($entry = $d->read())) 
               {
                   if($entry!='.' && $entry!='..') 
                   {
                       $entry = $dir.'/'.$entry;
                       if(is_dir($entry)) 
                       {
                           $subdirs = get_leaf_dirs($entry);
                           if ($subdirs)
                             $array = array_merge($array, $subdirs);
                           else
                             $array[] = $entry;
                       }
                   }
               }
               $d->close();
         }
         return $array;
      }
function directoryList($start,$win32=false){
    if($win32){
        $slash="\\";
    }else{
        $slash="/";
    }
    $basename = pathinfo($start);
    $basename = $basename['basename'];
    $ls=array();
    $dir = dir($start);
    while($item = $dir->read()){
        if(is_dir($start.$slash.$item)&& $item!="." && $item!=".."){
            $ls[$basename][]=directoryList($start.$slash.$item,$win32);
        }else{
            if($item!="."&&$item!=".."){
                $ls[$basename][]=$item;
            }
        }
    }
    return $ls;
}
$path = pathinfo(__FILE__);
$ls = directoryList($path['dirname'], true);
Regarding samuel's comment about the dir() function not supporting Unicode properly, it's all in the encoding. The function does NOT internally change Unicode characters into question marks (?), as I was first led to believe. If you simply try to output them in UTF-8, they'll show up just right.
Note that the dir object will use the default encoding for non-unicode programs on Windows with PHP 5.x.
So, if you have a file named with characters unsupported by the current default encoding, the dir->read() method will return a wrong entry.
<?php
/*
** This script is on the same directory than a file named with
** unsupported characters for the current default encoding.
*/
$d = dir("./");
while(false !== ($e = $d->read()))
    echo $e . '<br/>';
?>
This will print a "?" for every unsupported characters, and not the right file name. So take care if you check with is_file/is_dir right after enumerating.
Regarding jaqb's post about a correction to the read_dir function, I have one small fix too if people wish to also list the directories inside this directory and read them into the same array.
<?
function read_dir($dir) {
   $array = array();
   $d = dir($dir);
   while (false !== ($entry = $d->read())) {
       if($entry!='.' && $entry!='..') {
           $entry = $dir.'/'.$entry;
           if(is_dir($entry)) {
               $array[] = $entry;
               $array = array_merge($array, read_dir($entry));
           } else {
               $array[] = $entry;
           }
       }
   }
   $d->close();
   return $array;
}
?>
Saw the leaf dirs bit...  quick mod:
function preg_ls ($path=".", $rec=false, $pat="/.*/") {
    $pat=preg_replace ("|(/.*/[^S]*)|s", "\\1S", $pat);
    while (substr ($path,-1,1) =="/") $path=substr ($path,0,-1);
    if (!is_dir ($path) ) $path=dirname ($path);
    if ($rec!==true) $rec=false;
    $d=dir ($path);
    $ret=Array ();
    while (false!== ($e=$d->read () ) ) {
        if ( ($e==".") || ($e=="..") ) continue;
        if ($rec && is_dir ($path."/".$e) ) {
            $ret=array_merge ($ret,preg_ls($path."/".$e,$rec,$pat));
            continue;
        }
        if (!preg_match ($pat,$e) ) continue;
        $ret[]=$path."/".$e;
    }
    return (empty ($ret) && preg_match ($pat,basename($path))) ? Array ($path."/") : $ret;
}
example:
foreach (preg_ls ("/usr/share/fluxbox", true, "/[LT]e[sa]/i") as $file) echo $file."\n";
output:
/usr/share/fluxbox/styles/Leaf/
/usr/share/fluxbox/styles/Clean
/usr/share/fluxbox/styles/Testing/
This one's pretty nice.  After getting frustrated for hunting down .jpg files in my massive music collection (PHP would run out of memory), I thought there should be a preg_ls function.
function preg_ls ($path=".", $rec=false, $pat="/.*/") {
    // it's going to be used repeatedly, ensure we compile it for speed.
    $pat=preg_replace("|(/.*/[^S]*)|s", "\\1S", $pat);
    //Remove trailing slashes from path
    while (substr($path,-1,1)=="/") $path=substr($path,0,-1);
    //also, make sure that $path is a directory and repair any screwups
    if (!is_dir($path)) $path=dirname($path);
    //assert either truth or falsehoold of $rec, allow no scalars to mean truth
    if ($rec!==true) $rec=false;
    //get a directory handle
    $d=dir($path);
    //initialise the output array
    $ret=Array();
    //loop, reading until there's no more to read
    while (false!==($e=$d->read())) {
        //Ignore parent- and self-links
        if (($e==".")||($e=="..")) continue;
        //If we're working recursively and it's a directory, grab and merge
        if ($rec && is_dir($path."/".$e)) {
            $ret=array_merge($ret,preg_ls($path."/".$e,$rec,$pat));
            continue;
        }
        //If it don't match, exclude it
        if (!preg_match($pat,$e)) continue;
        //In all other cases, add it to the output array
        $ret[]=$path."/".$e;
    }
    //finally, return the array
    return $ret;
}
Not bad for a mere 18 lines, don't you think?
Example use:
foreach (preg_ls("/etc/X11", true, "/.*\.conf/i") as $file) echo $file."\n";
Output: 
/etc/X11/xkb/README.config
/etc/X11/xorg.conf-vesa
/etc/X11/xorg.conf~
/etc/X11/gui.conf
/etc/X11/xorg.conf
/etc/X11/xorg.conf-fbdev
i've modified the script below to get the leaf folders of any directory (folders with no subfolders).
note: this does not return the folder passed in as a parameter, even if it has no subfolders.
<?php
function get_leaf_dirs($dir) {
   $array = array();
   $d = dir($dir);
   while (false !== ($entry = $d->read())) {
       if($entry!='.' && $entry!='..') {
           $entry = $dir.'/'.$entry;
           if(is_dir($entry)) {
               $subdirs = get_leaf_dirs($entry);
               if ($subdirs)
                  $array = array_merge($array, $subdirs);
               else
                  $array[] = $entry;
           }
       }
   }
   $d->close();
   return $array;
}
?>