Executing a callback for all files in a directory
A common question when dealing with deep directory structures concerns how a function can be applied recursively to all the files in the target directory, regardless of the depth. This function, directory_walk
, builds an internal stack (uses no recursion) and iteratively applies the user supplied callback providing a fast and flexible approach.
<?php
/**
* Allows running a callback on all files in a deep directory structure
*
* @author Aidan Lister <aidan@php.net>
* @param string $dirname The directory to walk
* @param callable $callable The callable to execute on all files found
* @param mixed $arg{n} Extra parameters to be passed to the callable
* @return mixed The return value of the last callable run
*/
function directory_walk($dirname, $callable)
{
$ignore = array('.', '..', '.DS_Store');
$args = func_get_args();
array_shift($args);
// Sanity check
if (!file_exists($dirname)) {
return false;
}
// Create and iterate stack
$stack = array($dirname);
while ($entry = array_pop($stack)) {
if (is_link($entry)) continue;
// Run the action
if (is_file($entry)) {
$ret = call_user_func_array($callable, array($entry) + $args);
continue;
}
// Add the directory into the stack
$dh = opendir($entry);
while (false !== $child = readdir($dh)) {
if (in_array($child, $ignore)) continue;
$child = $entry . DIRECTORY_SEPARATOR . $child;
$stack[] = $child;
}
closedir($dh);
}
return $ret;
}
?>
So, for some examples. We’ll start simple and simply print the directory:
<?php
$func = create_function('$file', 'echo "found $file";');
directory_walk('target-dir', $func);
?>
Which for me outputs:
found target-dir/foo
found target-dir/bar
found target-dir/baz
found target-dir/baz/ding
found target-dir/baz/dong
found target-dir/baz/dong/witch
What if we wanted to add a .txt extension to all of these files? We could write:
<?php
function my_rename($entry, $extension) {
if (is_file($entry)) {
rename($entry, $entry . $extension);
}
}
directory_walk('target-dir', 'my_rename', '.txt');
?>
Another example might be deleting all the .svn or CVS folders in a directory. We could write:
<?php
function delsvn($file) {
$ext = substr($file, strlen($file)-4,strlen($file));
if ($ext === '.svn') rmdirr($ext);
}
directory_walk('test', 'delsvn');
?>
Note that the above example used the recursive delete function also.
Happy stacking.