PHP Advent Calendar Day 7

07 Dec 2007

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.

  1. <?php
  2.  
  3. class RecursiveFileIterator extends RecursiveIteratorIterator
  4. {
  5.     /**
  6.      * Takes a path to a directory, checks it, and then recurses into it.
  7.      * @param $path directory to iterate
  8.      */
  9.     public function __construct($path)
  10.     {
  11.         // Use realpath() and make sure it exists; this is probably overkill, but I'm anal.
  12.         $path = realpath($path);
  13.  
  14.         if (!file_exists($path)) {
  15.             throw new Exception("Path $path could not be found.");
  16.         } elseif (!is_dir($path)) {
  17.             throw new Exception("Path $path is not a directory.");
  18.         }
  19.  
  20.         // Use RecursiveDirectoryIterator() to drill down into subdirectories.
  21.         parent::__construct(new RecursiveDirectoryIterator($path));
  22.     }
  23. }
  24.  
  25. // This is how you use it.
  26. foreach (new RecursiveFileIterator('/path/to/something') as $item) {
  27.     // Because $item is actually an SPLFileInfo object, echo gives you the absolute path from __toString() magic.
  28.     echo $item . PHP_EOL;
  29. }
  30.  
  31. ?>

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.

  1. <?php
  2.  
  3. class RecursiveFileFilterIterator extends FilterIterator
  4. {
  5.     /**
  6.      * acceptable extensions - array of strings
  7.      */
  8.     protected $ext = array();
  9.  
  10.     /**
  11.      * Takes a path and shoves it into our earlier class.
  12.      * Turns $ext into an array.
  13.      * @param $path directory to iterate
  14.      * @param $ext comma delimited list of acceptable extensions
  15.      */
  16.     public function __construct($path, $ext = 'php')
  17.     {
  18.         $this->ext = explode(',', $ext);
  19.         parent::__construct(new RecursiveFileIterator($path));
  20.     }
  21.  
  22.     /**
  23.      * Checks extension names for files only.
  24.      */
  25.     public function accept()
  26.     {
  27.         $item = $this->getInnerIterator();
  28.  
  29.         // If it's not a file, accept it.
  30.         if (!$item->isFile()) {
  31.             return TRUE;
  32.         }
  33.  
  34.         // If it is a file, grab the file extension and see if it's in the array.
  35.         return in_array(pathinfo($item->getFilename(), PATHINFO_EXTENSION), $this->ext);
  36.     }
  37. }
  38.  
  39. // Same usage as above, but you can indicate allowed extensions with the optional second argument.
  40. foreach (new RecursiveFileFilterIterator('/path/to/something', 'php,txt') as $item) {
  41.     // This is an SPLFileInfo object.
  42.     echo $item . PHP_EOL;
  43. }
  44.  
  45. ?>

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.