About the Author

Chris Shiflett

Hi, I’m Chris: entrepreneur, community leader, husband, and father. I live and work in Boulder, CO.


PHP Advent Calendar Day 7

Today's entry, provided by Elizabeth Smith, is entitled SPL to the Rescue.

Elizabeth Smith

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.

About this post

PHP Advent Calendar Day 7 was posted on Fri, 07 Dec 2007. If you liked it, follow me on Twitter or share:

3 comments

1.Davey Shafik said:

We need a built in RecursiveDirectoryIteratorIterator - this is a common enough use-case to be included in SPL-proper IMO :)

- Davey

Fri, 07 Dec 2007 at 18:48:55 GMT Link


2.Adam said:

It does look very in-depth. Some impressive PHP right there!

Adam @ http://www.talkphp.com/

Sat, 08 Dec 2007 at 00:43:41 GMT Link


3.Jakob said:

I don't think the RecursiveFileIterator class is really needed. If you need this kind of functionality you can just iterate over an recursiveIteratorIterator which iterates a RecursiveDirectoryIterator. This is exactly what the created class does, but it could be done right inside the foreach loop.

foreach( new RecursiveIteratorIterator(
 
                new RecursiveDirectoryIterator(
 
                    "Your directory goes here"
 
                )
 
          ) as $item ) {
 
    echo $item, PHP_EOL;
 
}

At first it looks kind of strange but you will get used to it quite fast.

I really like this little article of yours, because it shows other php developers what the spl can do and how it's features make the life of a developer a lot easier.

- Jakob

Sat, 08 Dec 2007 at 17:00:46 GMT Link


Hello! What’s your name?

Want to comment? Please connect with Twitter to join the discussion.