PHP Advent Calendar Day 7
07 Dec 2007Today's entry, provided by Elizabeth Smith, is entitled SPL to the Rescue.
- Name
- Elizabeth Smith
- Blog
- elizabethmariesmith.com
- Biography
- Elizabeth Smith is a PHP Windows geek, lover of all things PECL, PHPWomen.org charter member, PHP-GTK 2 developer, and generally involved in doing bad things with PHP for fun and profit.
- Location
- Sturgis, Michigan
I like doing command line scripting with PHP; it's fun and simple. Often, I need to manipulate batches of files by recursively iterating over files in a directory and all subdirectories. Of course, I want to do it quickly and efficiently, so I can get my Christmas shopping done. Long ago, there was opendir()
, and then came scandir()
, but to make these functions recursive involved convoluted looping that makes my head hurt. I try to avoid recursive functions, probably because I also tend to forget something and end up with infinite loops. PHP 5 with SPL has really nifty tools to make life easier, I promise! Below is my solution for recursively iterating over files in any directory. I'll give it the exciting name of RecursiveFileIterator
.
<?php
class RecursiveFileIterator extends RecursiveIteratorIterator
{
/**
* Takes a path to a directory, checks it, and then recurses into it.
* @param $path directory to iterate
*/
public function __construct($path)
{
// Use realpath() and make sure it exists; this is probably overkill, but I'm anal.
$path = realpath($path);
if (!file_exists($path)) {
throw new Exception("Path $path could not be found.");
} elseif (!is_dir($path)) {
throw new Exception("Path $path is not a directory.");
}
// Use RecursiveDirectoryIterator() to drill down into subdirectories.
parent::__construct(new RecursiveDirectoryIterator($path));
}
}
// This is how you use it.
foreach (new RecursiveFileIterator('/path/to/something') as $item) {
// Because $item is actually an SPLFileInfo object, echo gives you the absolute path from __toString() magic.
echo $item . PHP_EOL;
}
?>
See what little code that takes? I added lots of comments (because I'm comment crazy) and checks (because I'm like that when I code), but can you imagine if I had used recursive functions and scandir()
?
I'm being extremely boring and simply echoing the absolute path to the file. You aren't limited to that; you actually get an SPLFileInfo
instance when you use RecursiveDirectoryIterator()
, and it gives you all sorts of goodies such as timestamps, permissions, owner, and more.
When performing voodoo like this, I usually want to not only recursively iterate over files, but also pick which files to look at as well. You can do this with the magic of glob()
, but there are some drawbacks. Firstly, glob()
is not consistent cross-platform, and secondly, glob()
can be really slow, especially on Windows. I prefer to use the magic of SPL's FilterIterator
combined with the code above. Your accept()
method can be anything you like; below is my generic version for file extension checking. I'll give it the exciting name of RecursiveFileFilterIterator
. Just tell it what file extensions to allow and away you go.
<?php
class RecursiveFileFilterIterator extends FilterIterator
{
/**
* acceptable extensions - array of strings
*/
protected $ext = array();
/**
* Takes a path and shoves it into our earlier class.
* Turns $ext into an array.
* @param $path directory to iterate
* @param $ext comma delimited list of acceptable extensions
*/
public function __construct($path, $ext = 'php')
{
$this->ext = explode(',', $ext);
parent::__construct(new RecursiveFileIterator($path));
}
/**
* Checks extension names for files only.
*/
public function accept()
{
$item = $this->getInnerIterator();
// If it's not a file, accept it.
if (!$item->isFile()) {
return TRUE;
}
// If it is a file, grab the file extension and see if it's in the array.
return in_array(pathinfo($item->getFilename(), PATHINFO_EXTENSION), $this->ext);
}
}
// Same usage as above, but you can indicate allowed extensions with the optional second argument.
foreach (new RecursiveFileFilterIterator('/path/to/something', 'php,txt') as $item) {
// This is an SPLFileInfo object.
echo $item . PHP_EOL;
}
?>
Notice again the small amount of code. The next time you want to do something to a bunch of PHP files, let SPL come to your rescue.