skip to content

PHP: Directory Listing using SPL

This article continues on from the earlier PHP Directory Listing article and outlines how to make use of the new SPL classes in PHP for listing and filtering files.

The modern (SPL) approach

With PHP 5 we have access to a whole range of new constructs and helpers, including SPL data structures, classes and iterators. Here we've taken the basic directory listing function from above and converted it to use SPL.

<?PHP use \DirectoryIterator; function getFileList($dir) { // array to hold return value $retval = []; // add trailing slash if missing if(substr($dir, -1) != "/") { $dir .= "/"; } // open directory for reading $d = new DirectoryIterator($dir) or die("getFileList: Failed opening directory $dir for reading"); foreach($d as $fileinfo) { // skip hidden files if($fileinfo->isDot()) { continue; } $retval[] = [ 'name' => "{$dir}{$fileinfo}", 'type' => ($fileinfo->getType() == "dir") ? "dir" : mime_content_type($fileinfo->getRealPath()), 'size' => $fileinfo->getSize(), 'lastmod' => $fileinfo->getMTime() ]; } return $retval; } ?>

The elements returned by the DirectoryIterator are instances of the SplFileInfo class from which we can then access file information.

The usage for this function is exactly the same as for the previous version which didn't use SPL:

<?PHP // examples for scanning the current directory $dirlist = getFileList("."); $dirlist = getFileList("./"); // examples for scanning a directory called images $dirlist = getFileList("images"); $dirlist = getFileList("images/"); $dirlist = getFileList("./images"); $dirlist = getFileList("./images/"); ?>

source code for this example.

The main difference with this approach is that instead of calling separate functions to get the file details we can use methods of the SPLFileInfo class. The exception being mime_content_type.

This is only scratching the surface of SPL. The Iterators are very powerful in themselves, and while SPLFileInfo is fairly basic, you can replace it with SPLFileObject in a similar context and use its methods to not only examine, but also to open, read and modify files in the directory.

Applying a FilterIterator

To illustrate the power of the SPL Iterator classes, consider the following. We've added a parameter to the file listing function where we can specify the file extension to match, and only those files will be returned.

First we need to create a new iterator that can filter the output of our existing DirectoryIterator:

<?PHP class FileExtFilter extends FilterIterator { private $fileext; public function __construct(Iterator $iterator, $fileext) { parent::__construct($iterator); $this->fileext = $fileext; } public function accept() { $file = $this->getInnerIterator()->current(); return preg_match("/\.({$this->fileext})$/", $file); } } ?>

To apply the new filter we just add it inline to the code, passing the previous iterator as the first parameter:

<?PHP function getFilteredFileList($dir, $ext) { $retval = []; // add trailing slash if missing if(substr($dir, -1) != "/") { $dir .= "/"; } // open directory for reading $d = new DirectoryIterator($dir) or die("getFilteredFileList: Failed opening directory $dir for reading"); $iterator = new FileExtFilter($d, $ext); foreach($iterator as $fileinfo) { // skip hidden files if($fileinfo->isDot()) { continue; } $retval[] = [ 'name' => "{$dir}{$fileinfo}", 'type' => ($fileinfo->getType() == "dir") ? "dir" : mime_content_type($fileinfo->getRealPath()), 'size' => $fileinfo->getSize(), 'lastmod' => $fileinfo->getMTime() ]; } return $retval; } ?>

Wheras in the previous example we looped through the DirectoryIterator object, we're now encasing that iterator in a FilterIterator which performs the simple function of screening files based on their extension.

The extension to match is included in the function call and can include (PCRE) regular expressions, for example:

<?PHP $dirlist = getFilteredFileList("images/", "png"); // png $dirlist = getFilteredFileList("images/", "jpe?g"); // jpg, jpeg $dirlist = getFilteredFileList("files/", "pdf|docx?"); // pdf, doc, docx ?>

source code for this example.

To use this technique with recursion you should explore the RecursiveDirectoryIterator class which has amazing capabilities.

Using the RegexIterator

Instead of using the FilterIterator to apply regular expressions, we can use the more powerful RegexIterator to let us apply a regular expression to the full file name.

While this sounds complicated, it actually results in much simpler code than what we had previously:

<?PHP function getFilteredFileList($dir, $regex) { $retval = []; // add trailing slash if missing if(substr($dir, -1) != "/") { $dir .= "/"; } // open directory for reading $d = new DirectoryIterator($dir) or die("getFilteredFileList: Failed opening directory $dir for reading"); $iterator = new RegexIterator($d, $regex, RegexIterator::MATCH); foreach($iterator as $fileinfo) { // skip hidden files if($fileinfo->isDot()) { continue; } $retval[] = [ 'name' => "{$dir}{$fileinfo}", 'type' => ($fileinfo->getType() == "dir") ? "dir" : mime_content_type($fileinfo->getRealPath()), 'size' => $fileinfo->getSize(), 'lastmod' => $fileinfo->getMTime() ]; } return $retval; } ?>

Our function now accepts a regular expression as the second parameter, for example:

<?PHP $dirlist = getFilteredFileList("images/", "/\.png$/"); $dirlist = getFilteredFileList("images/", "/^html5.*\.png$/"); ?>

The latter will return all PNG files in the images directory where the filename also starts with 'html5':

Array ( [0] => images/html5-required.png [1] => images/html5-email.png [2] => images/html5-demo.png [3] => images/html5-number.png [4] => images/html5-number-opera.png )

Using the GlobIterator

For the specific task of matching files based on filename it's recommended that you use the GlobIterator.

The advantage over the FilterIterator and RegexIterator is that rather than selecting all files in the directory and then iterating over just a subset, using 'glob' we can access directly the matching files.

<?PHP function getFilteredFileList($pattern) { $retval = []; try { $g = new \GlobIterator($pattern); } catch(\RuntimeException $e) { // handle exception } foreach($g as $fileinfo) { $retval[] = [ 'name' => "$fileinfo", 'type' => ($fileinfo->getType() == "dir") ? "dir" : mime_content_type($fileinfo->getRealPath()), 'size' => $fileinfo->getSize(), 'lastmod' => $fileinfo->getMTime() ]; } return $retval; } ?>

The catch clause will catch any Exception thrown by trying to open a non-existent or unreadable directory. You should put your own handler there.

The method of calling is similar to before, just using filesystem 'glob' patterns rather than regular expressions:

<?PHP $dirlist = getFilteredFileList("./images/html5*.png"); $dirlist = getFilteredFileList("images/html5*.png"); $dirlist = getFilteredFileList("files/*.doc*"); $dirlist = getFilteredFileList("{$_SERVER['DOCUMENT_ROOT']}/files/*.pdf"); ?>

There are many ways of combining iterators to filter results. Also a range of recursive iterators.

Sorting the output

And now a piece of SPL magic to return a list of files that can be sorted by any of the available attributes (name, type, size, or lastmod).

<?PHP namespace Chirp; // Original PHP code by Chirp Internet: www.chirpinternet.eu // Please acknowledge use of this code by including this header. class FieldSortHeap extends \SplHeap { private $sortField; public function __construct($sortField) { $this->sortField = $sortField; } protected function compare($a, $b) { return strnatcmp($b[$this->sortField], $a[$this->sortField]); } } function getFilteredFileList($pattern, $orderby) { $retval = new FieldSortHeap($orderby); try { $g = new \GlobIterator($pattern); } catch(\RuntimeException $e) { // handle exception } foreach($g as $fileinfo) { $retval->insert([ 'name' => "$fileinfo", 'type' => ($fileinfo->getType() == "dir") ? "dir" : mime_content_type($fileinfo->getRealPath()), 'size' => $fileinfo->getSize(), 'lastmod' => $fileinfo->getMTime() ]); } return $retval; }

The main change here is to replace the PHP Array we were using to store the file details with an SplHeap object.

In initialising the heap we pass it a field name for the compare method and after that it takes care of arranging the files in order as they are inserted into the heap.

The disadvantage of a heap is that you can only traverse it in one direction (in this case lowest to highest) and not extract arbitrary elements. Also by traversing the heap you are actually destroying it, so it can't be used a second time without being rebuilt.

In this example we're retrieving and displaying a list of PNG files sorted by file size:

<table border="1"> <thead> <tr> <th></th> <th>Name</th> <th>Type</th> <th>Size</th> <th>Last Modified</th> </tr> </thead> <tbody> <?PHP $dirlist = getFilteredFileList('images/*.png', 'size'); foreach($dirlist as $file) { echo "<tr>\n"; echo "<td><img src=\"{$file['name']}\" width=\"64\"></td>\n"; echo "<td>{$file['name']}</td>\n"; echo "<td>{$file['type']}</td>\n"; echo "<td>{$file['size']}</td>\n"; echo "<td>",date('r', $file['lastmod']),"</td>\n"; echo "</tr>\n"; } ?> </tbody> </table>

source code for this example.

We can just as easily sort the file list by name, type or date last modified by changing the function call to 'name', 'type' or 'lastmod' respectively.

If you want to be able to reuse the output, or to be able to use the normal PHP array functions, then you are better off leaving the output as a simple Array and using one of our array sorting functions to change the order before displaying the list.

< PHP

User Comments

Post your comment or question

6 March, 2018

hi i used the above code in the line
$retval[] = ['name' => "$fileinfo"
returned the full file directory list I just need the file name to be displayed

$retval[] = ['name' => $fileinfo->getBasename()]

4 March, 2015

I love the php and the various options for listing a directory, but I'm in kind of a tight spot. I'm using the SPL example with the sorting and it works great for a single directory. Not knowing php very much, I was hoping that there would be an example of the RecursiveDirectoryIterat­or option to copy.

Barring that, I thought that perhaps I could just call the routine above several times, one for each directory, but the array listing only works once in the same page that calls it.

Can you post a sorting version that can read multiple directories?

7 November, 2014

Hello,

Thank you so much for the amazing tutorials. I used the one below for an internal site directory.
www.the-art-of-web.com/php/dirlist/

It works great!

I'm wondering how to add other file formats, such as Powerpoint, .pptx. Is this possible? I'm not very PHP savvy.

Thank you for your time!
Rebecca

Yes, it should be a simple matter of just changing the file type (e.g. 'application/pdf') or extension (e.g. '.pdf') to match Powerpoint files (e.g. 'application/vnd.openxmlformats-officedocument.presentationml.presentation').

top