-
-
Save mattpass/3035508 to your computer and use it in GitHub Desktop.
<?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>"; | |
?> |
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)
- 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
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++;}
Now available as a git repo:
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.==
and<
. Do you know"9" == "9a"
returns true? Usestrcmp
instead.