Skip to content

Instantly share code, notes, and snippets.

@mattpass
Created July 2, 2012 20:28
Show Gist options
  • Save mattpass/3035508 to your computer and use it in GitHub Desktop.
Save mattpass/3035508 to your computer and use it in GitHub Desktop.
PHP: Show dir tree as a UL list
<?php
// Function to sort given values alphabetically
function alphasort($a, $b) {
return strcasecmp($a->getPathname(), $b->getPathname());
}
// Class to put forward the values for sorting
class SortingIterator implements IteratorAggregate {
private $iterator = null;
public function __construct(Traversable $iterator, $callback) {
$array = iterator_to_array($iterator);
usort($array, $callback);
$this->iterator = new ArrayIterator($array);
}
public function getIterator() {
return $this->iterator;
}
}
// Get a full list of dirs & files and begin sorting using above class & function
$path = $_SERVER['DOCUMENT_ROOT'];
$objectList = new SortingIterator(new RecursiveIteratorIterator(new RecursiveDirectoryIterator($path), RecursiveIteratorIterator::SELF_FIRST), 'alphasort');
// With that done, create arrays for out final ordered list and a temp array of files to copy over
$finalArray = $tempArray = array();
// To start, push folders from object into finalArray, files into tempArray
foreach ($objectList as $objectRef) {
$fileFolderName = rtrim(substr($objectRef->getPathname(), strlen($path)),"..");
if ($objectRef->getFilename()!="." && $fileFolderName[strlen($fileFolderName)-1]!="/") {
$fileFolderName!="/" && is_dir($path.$fileFolderName) ? array_push($finalArray,$fileFolderName) : array_push($tempArray,$fileFolderName);
}
}
// Now push root files onto the end of finalArray and splice from the temp, leaving only files that reside in subsirs
for ($i=0;$i<count($tempArray);$i++) {
if (count(explode("/",$tempArray[$i]))==2) {
array_push($finalArray,$tempArray[$i]);
array_splice($tempArray,$i,1);
$i--;
}
}
// Lastly we push remaining files into the right subdirs in finalArray
for ($i=0;$i<count($tempArray);$i++) {
$insertAt = array_search(dirname($tempArray[$i]),$finalArray)+1;
for ($j=$insertAt;$j<count($finalArray);$j++) {
if ( strcasecmp(dirname($finalArray[$j]), dirname($tempArray[$i]))==0 &&
strcasecmp(basename($finalArray[$j]), basename($tempArray[$i]))<0 ||
strstr(dirname($finalArray[$j]),dirname($tempArray[$i]))) {
$insertAt++;
}
}
array_splice($finalArray, $insertAt, 0, $tempArray[$i]);
}
// Finally, we have our ordered list, so display in a UL
echo "<ul>\n<li>/</li>\n";
$lastPath="";
for ($i=0;$i<count($finalArray);$i++) {
$fileFolderName = $finalArray[$i];
$thisDepth = count(explode("/",$fileFolderName));
$lastDepth = count(explode("/",$lastPath));
if ($thisDepth > $lastDepth) {echo "<ul>\n";}
if ($thisDepth < $lastDepth) {
for ($j=$lastDepth;$j>$thisDepth;$j--) {
echo "</ul>\n";
}
}
echo "<li>".basename($fileFolderName)."</li>\n";
$lastPath = $fileFolderName;
}
echo "</ul>\n</ul>";
?>
@maettig
Copy link

maettig commented Jul 4, 2012

  • You know this is not "without recursion"? You are using two "Recursive" iterators. ;-)
  • Is there a reason you are using PHP_EOL instead of \n? Just wondering. If you ask me, I don't care about well-formated HTML. All these newlines do are wasting bytes.
  • Avoid comparing strings with == and <. Do you know "9" == "9a" returns true? Use strcmp instead.
function alphasort($a, $b) {
    return strcasecmp($a->getPathname(), $b->getPathname());
}

@mattpass
Copy link
Author

mattpass commented Jul 4, 2012

The results are in! I've compared this method to the usual recursive methods such as scan dir and this method is twice as fast:

Usual recursive method (scandir etc)
6.51, 6.27, 6.92, 6.56 and 6.73 secs
= 32.99 secs = 6.59 secs avg

The above method iteration method
3.38, 3.26, 3.24, 3.40 and 3.23 secs
= 16.51 secs = 3.30 secs avg

(Tested 5 times in a row on a server with 2,120 files in 248 folders with a size of ~110mb)

@mattpass
Copy link
Author

mattpass commented Jul 4, 2012

@maettig

  • Erm, yes, it of course still has to recursively gather the dirs & files in each dir. The above version tho it is recursively collecting the tree info into an object using PHP's own functions, better than you sticking together your own DIY version (which involves recursively calling a function to get a single dir list, and recursively building up a tree as you go). Thats what I meant. ;)
  • Now using \n instead of PHP_EOL. I need to see formatted HTML often when dumping a whole load of code so I'll keep linebreaks in there.
  • Good tip on strcasecmp, now using that instead.
  • I think the string comparison line you're referring to is

if (dirname($finalArray[$j])==dirname($tempArray[$i]) && basename($finalArray[$j]) < basename($tempArray[$i])

Can I use strcasecomp here then to check for -1, 0 or 1? ie:

if (strcasecomp(dirname($finalArray[$j]),dirname($tempArray[$i]))===0 && strcasecomp(basename($finalArray[$j]), basename($tempArray[$i]))===-1

@mattpass
Copy link
Author

mattpass commented Jul 5, 2012

Tested using strcasecmp rather than comparing strings on == and < and works fine, added this to final solution:

if ( strcasecmp(dirname($finalArray[$j]), dirname($tempArray[$i]))==0 && strcasecmp(basename($finalArray[$j]), basename($tempArray[$i]))<0 || strstr(dirname($finalArray[$j]),dirname($tempArray[$i]))) {$insertAt++;}

@mattpass
Copy link
Author

mattpass commented Jul 7, 2012

Now available as a git repo:

https://github.com/mattpass/dirTree

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment