PHP: Directory Listing

PHP: Directory Listing

A common request is to be able to produce a list of some or all of
the files in a directory – similar to the default index page provided by
most web servers, but with more control over the content and formatting.

Another goal might be to take some action on the files using PHP.
In either case the first step is to query the file system to return a
list of directories and files.

The functions presented below provide the means to extract the file
names and other properties from a single directory, or to explore
subdirectories recursively.

PHP 5 introduces the scandir function which will “List files and
directories inside the specified path” but won’t recurse or
provide additional information about the listed files.

If you are using PHP 5 or higher check out our new article on Directory Listing using SPL
classes
for a much neater approach, and our new FileList PHP class.

1. Single Directory Listing

To get started, here is a simple function that returns a list of
files, directories and their properties from a single directory (more
advanced versions of this function can be found further down the
page):

<?PHP
function getFileList($dir)
{
// array to hold return value
$retval = [];

// add trailing slash if missing
if(substr($dir, -1) != “/”) {
$dir .= “/”;
}

// open pointer to directory and read list of files
$d = @dir($dir) or die(“getFileList: Failed opening directory {$dir} for reading”);
while(FALSE !== ($entry = $d->read())) {
// skip hidden files
if($entry{0} == “.”) continue;
if(is_dir(“{$dir}{$entry}”)) {
$retval[] = [
‘name’ => “{$dir}{$entry}/”,
‘type’ => filetype(“{$dir}{$entry}”),
‘size’ => 0,
‘lastmod’ => filemtime(“{$dir}{$entry}”)
];
} elseif(is_readable(“{$dir}{$entry}”)) {
$retval[] = [
‘name’ => “{$dir}{$entry}”,
‘type’ => mime_content_type(“{$dir}{$entry}”),
‘size’ => filesize(“{$dir}{$entry}”),
‘lastmod’ => filemtime(“{$dir}{$entry}”)
];
}
}
$d->close();

return $retval;
}
?>

You can use this function as follows:

<?PHP
// list files in the current directory
$dirlist = getFileList(".");
$dirlist = getFileList("./");

// a subdirectory of the current directory called images
$dirlist = getFileList(“images”);
$dirlist = getFileList(“images/”);
$dirlist = getFileList(“./images”);
$dirlist = getFileList(“./images/”);

// using an absolute path
$dirlist = getFileList(“{$_SERVER[‘DOCUMENT_ROOT’]}/images”);
$dirlist = getFileList(“{$_SERVER[‘DOCUMENT_ROOT’]}/images/”);
?>

The variable $_SERVER['DOCUMENT_ROOT'] should resolve to the root directory of your website. e.g. /var/www/public_html

The return value is an associative array of files including the
filepath, type, size and last modified date, except when a file is
actually a directory, in that case the string “(dir)” appears
instead of the filesize. The filenames take the same stem as the
function call:

Example 1:

<?PHP
$dirlist = getFileList("images");
echo "<pre>",print_r($dirlist),"</pre>";

/* sample output

Array
(
[0] => Array
(
[name] => images/background0.jpg
[type] => image/jpeg
[size] => 86920
[lastmod] => 1077461701
)

[1] => …
)

*/
?>

Example 2:

<?PHP
$dirlist = getFileList("./images");
echo "<pre>",print_r($dirlist),"</pre>";

/* sample output

Array
(
[0] => Array
(
[name] => ./images/background0.jpg
[type] => image/jpeg
[size] => 86920
[lastmod] => 1077461701
)

[1] => …
)

*/
?>

If you want the output sorted by one or more fields, you should read
the article on Sorting Arrays of Arrays or
try out one of our DHTML Sorting
Algorithms
using JavaScript.

We also have an article on Directory Listing using SPL classes
(DirectoryIterator and SplFileInfo) which introduces many new options
for filtering and sorting the output.

2. Displaying File List in HTML

To output the results to an HTML page we just loop through the
returned array:

<?PHP
// output file list in HTML TABLE format
echo "<table border=\"1\">\n";
echo "<thead>\n";
echo "<tr><th>Name</th><th>Type</th><th>Size</th><th>Last Modified</th></tr>\n";
echo "</thead>\n";
echo "<tbody>\n";
foreach($dirlist as $file) {
echo "<tr>\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";
}
echo "</tbody>\n";
echo "</table>\n\n";
?>

This code can be easily modified to: make the output a list instead
of a table; make the file names actual links; replace the names with
icons based on file type or extension; etc.

Display PNG images in a TABLE:

For example, to display only PNG files, just add a condition to the
output loop:

<?PHP
// output file list as HTML table
echo "<table border=\"1\">\n";
echo "<thead>\n";
echo "<tr><th></th><th>Name</th><th>Type</th><th>Size</th><th>Last Modified</th></tr>\n";
echo "</thead>\n";
echo "<tbody>\n";
foreach($dirlist as $file) {
if(!preg_match("/\.png$/", $file['name'])) {
continue;
}
echo "<tr>\n";
echo "<td><img src=\"{$file['name']}\" width=\"64\" alt=\"\"></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";
}
echo "</tbody>\n";
echo "</table>\n\n";
?>

Here you can view the complete source code for this example.

This will have the effect of skipping all files whose name does not
end with .png. You could also apply conditions based on the
file type, size, or last modified timestamp.

List PDF files with links:

One last example, listing only PDF files and having the file name
link to the file:

<table border="1">
<thead>
<tr><th>Name</th><th>Type</th><th>Size</th><th>Last Modified</th></tr>
</thead>
<tbody>
<?PHP
// output file list as table rows
foreach($dirlist as $file) {
if($file['type'] != 'application/pdf') {
continue;
}
echo "<tr>\n";
echo "<td><a href=\"{$file['name']}\">",basename($file['name']),"</a></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>

Here you can view the complete source code for this example.

If you want to display, for example, a thumbnail as a link to a
larger image, or even a video, just give the two files the same name and
in the script above use str_replace or similar function to modify either the
link href or the link contents. See our article on listing images for examples.

Using the SPL DirectoryIterator and FilterIterator classes we can
now specify a pattern to match when accessing the file list so only
matching files are returned. More on that here.

3. Recursive Directory Listing

Now that we’ve got this far, it’s only a minor change to extend the
function in order to recursively list any subdirectories. By adding a
second parameter to the function we also retain the previous
functionality of listing a single directory.

<?PHP
// Original PHP code by Chirp Internet: www.chirpinternet.eu
// Please acknowledge use of this code by including this header.

function getFileList($dir, $recurse = FALSE)
{
$retval = [];

// add trailing slash if missing
if(substr($dir, -1) != “/”) {
$dir .= “/”;
}

// open pointer to directory and read list of files
$d = @dir($dir) or die(“getFileList: Failed opening directory {$dir} for reading”);
while(FALSE !== ($entry = $d->read())) {
// skip hidden files
if($entry{0} == “.”) continue;
if(is_dir(“{$dir}{$entry}”)) {
$retval[] = [
‘name’ => “{$dir}{$entry}/”,
‘type’ => filetype(“{$dir}{$entry}”),
‘size’ => 0,
‘lastmod’ => filemtime(“{$dir}{$entry}”)
];
if($recurse && is_readable(“{$dir}{$entry}/”)) {
$retval = array_merge($retval, getFileList(“{$dir}{$entry}/”, TRUE));
}
} elseif(is_readable(“{$dir}{$entry}”)) {
$retval[] = [
‘name’ => “{$dir}{$entry}”,
‘type’ => mime_content_type(“{$dir}{$entry}”),
‘size’ => filesize(“{$dir}{$entry}”),
‘lastmod’ => filemtime(“{$dir}{$entry}”)
];
}
}
$d->close();

return $retval;
}
?>

To make use of the new functionality, you need to pass a value of TRUE
(or 1) as the second parameter.

<?PHP
// single directory
$dirlist = getFileList("./");

// include subdirectories
$dirlist = getFileList(“./”, TRUE);
?>

Before recursing the script first checks whether sub-directories are
readable, and otherwise moves on to the next item so as to
avoid permission errors.

As before, the return value is an array of associative arrays. In
fact the only change is that you have the additional option of a
recursive listing.

4. Limited Depth Recursion

This final example adds another feature – the ability to specify how
deep you want the recursion to go. The previous code would continue to
explore directories until it ran out of places to go. With this script
you can tell it to not go deeper than a fixed number of levels in the
file system.

<?PHP
// Original PHP code by Chirp Internet: www.chirpinternet.eu
// Please acknowledge use of this code by including this header.

function getFileList($dir, $recurse = FALSE, $depth = FALSE)
{
$retval = [];

// add trailing slash if missing
if(substr($dir, -1) != “/”) {
$dir .= “/”;
}

// open pointer to directory and read list of files
$d = @dir($dir) or die(“getFileList: Failed opening directory {$dir} for reading”);
while(FALSE !== ($entry = $d->read())) {
// skip hidden files
if($entry{0} == “.”) continue;
if(is_dir(“{$dir}{$entry}”)) {
$retval[] = [
‘name’ => “{$dir}{$entry}/”,
‘type’ => filetype(“{$dir}{$entry}”),
‘size’ => 0,
‘lastmod’ => filemtime(“{$dir}{$entry}”)
];
if($recurse && is_readable(“{$dir}{$entry}/”)) {
if($depth === FALSE) {
$retval = array_merge($retval, getFileList(“{$dir}{$entry}/”, TRUE));
} elseif($depth > 0) {
$retval = array_merge($retval, getFileList(“{$dir}{$entry}/”, TRUE, $depth-1));
}
}
} elseif(is_readable(“{$dir}{$entry}”)) {
$retval[] = [
‘name’ => “{$dir}{$entry}”,
‘type’ => mime_content_type(“{$dir}{$entry}”),
‘size’ => filesize(“{$dir}{$entry}”),
‘lastmod’ => filemtime(“{$dir}{$entry}”)
];
}
}
$d->close();

return $retval;
}
?>

As before we’ve added a single new parameter and a few lines of code.
The default value of the depth parameter, if not defined in the
function call, is set to FALSE. This ensures that all previous
features remain and that any legacy code won’t break when the function
is changed.

In other words, we can now call the getFileList function with one,
two or three parameters:

<?PHP
// single directory
$dirlist = getFileList("./");

// include all subdirectories recursively
$dirlist = getFileList(“./”, TRUE);

// include just one or two levels of subdirectories
$dirlist = getFileList(“./”, TRUE, 1);
$dirlist = getFileList(“./”, TRUE, 2);
?>

This is a good example of how a function can evolve over time without
becoming unmanageable. Too often you see functions that were once
useful become unusable because of parameter bloat.

5. Fileinfo replaces mime_content_type

The mime_content_type function has been deprecated in recent
version of PHP and replaced with the PECL Fileinfo extension
was never actually
deprecated, despite announcements to the contrary.

If you are seeing errors in your program due to this, the following
patch should work:

<?PHP
function getFileList($dir)
{

if(function_exists(‘mime_content_type’))
$finfo = FALSE;
} else {
$finfo = finfo_open(FILEINFO_MIME_TYPE);
}

// array to hold return value
$retval = [];

// add trailing slash if missing
if(substr($dir, -1) != “/”) {
$dir .= “/”;
}

// open pointer to directory and read list of files
$d = @dir($dir) or die(“getFileList: Failed opening directory {$dir} for reading”);
while(FALSE !== ($entry = $d->read())) {
// skip hidden files
if($entry{0} == “.”) continue;
if(is_dir(“{$dir}{$entry}”)) {
$retval[] = [
‘name’ => “{$dir}{$entry}/”,
‘type’ => filetype(“{$dir}{$entry}”),
‘size’ => 0,
‘lastmod’ => filemtime(“{$dir}{$entry}”)
];
} elseif(is_readable(“{$dir}{$entry}”)) {
$retval[] = [
‘name’ => “{$dir}{$entry}”,
‘type’ => ($finfo) ? finfo_file($finfo, “{$dir}{$entry}”) : mime_content_type(“{$dir}{$entry}”),
‘size’ => filesize(“{$dir}{$entry}”),
‘lastmod’ => filemtime(“{$dir}{$entry}”)
];
}
}
$d->close();

return $retval;
}
?>

This is the same function as seen at the top of the page, just with a
check for the mime_content_type function inserted at the top to
determine which method we use to get the file type.

If you are scanning a complex directory system you probably want to
declare $finfo as a global variable to avoid having to
re-instantiate it when the function recurses.

6. References


发表评论

电子邮件地址不会被公开。 必填项已用*标注