About the Author

Chris Shiflett

Hi, I'm Chris, a web developer and a founding member of Analog. I live and work in Brooklyn, NY.


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 at 16:09:23 GMT.

3 Comments

1. Davey Shafik's GravatarDavey 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's GravatarAdam 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's GravatarJakob 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


Post A Comment

Personal Details and Comment

Style Guide

Line breaks are converted to paragraphs. Also use:

  • <a href="" title="">text</a>1
  • <em>text</em>
  • <blockquote><p>text</p></blockquote>
  • <code>2  <?php  if ($foo) {      $foo = TRUE;  }  ?></code>
  1. Note: <code> can be used inline (e.g. in paragraphs) or in a block as shown. Include whitespace and newlines in blocks.

Please enter Chris (my first name) below. This is a primitive spam prevention technique, and I apologize for the inconvenience.

Preview and Submit

Upcoming Talks

ConFoo

10 - 12 Mar 2010

At Hilton Montréal Bonaventure, Montréal, Canada.

South by Southwest

12 - 16 Mar 2010

At Austin Convention Center, Austin, Texas.

Dutch PHP Conference

10 - 12 Jun 2010

At TBD, Amsterdam, Netherlands.

O'Reilly Open Source Convention

19 - 23 Jul 2010

At Oregon Convention Center, Portland, Oregon.

New Comments

Niall Kelly wrote:

Having tried other methods without success and looked through plenty of bloated documentation, th...

Posted in Git on Snow Leopard
liukang wrote:

I have problem with this example. In my php.ini magic_quotes_gpc is off so i'm using only addsla...

Posted in addslashes() Versus mysql_real_escape_string()
RyanTheGreat wrote:

Well, I'm not Chris, but I will do my best to address the questions raised in the comments by Ian...

Posted in Security Corner: Cross-Site Request Forgeries
Chris Shiflett wrote:

Thanks for the kind words, Simon. I'm glad you liked the tutorial. In case it's helpful, here'...

Posted in Webstock
Chris Shiflett wrote:

Hi Robin, I plan to post something about it, but it's going to be hard to express everything i...

Posted in Webstock

Browse Comments


Work and Books

Analog Essential PHP Security HTTP Developer's Handbook