Porto

Introduction to SPL DirectoryIterator

Introduction to SPL DirectoryIterator

Introduction to Standard PHP Library ( SPL )

By Kevin Waterson

Contents

  1. SPL
  2. Directory Iterator
  3. Reflection
  4. Exceptions
  5. getFilename
  6. getBasename
  7. isDot
  8. isDir
  9. Get File Last Access Time
  10. Get Inode Modification Time
  11. Get Group
  12. Get Inode
  13. Get Last Modification Time Of File
  14. Get File Owner
  15. Get File Path
  16. Get Path Name
  17. Get File Permissions
  18. Get File Size
  19. Get File Type
  20. Is Executable
  21. Is File
  22. Is Symbolic Link
  23. Is Readable
  24. Is Writable
  25. __toString
  26. Seek
  27. Rewind
  28. Get Symbolic Link Target
  29. Get Real Path
  30. Summary
  31. Credits

Directory Iterator

The DirectoryIterator in its simplest form will do some magic on its own, this built in class can be extended to do a lot more. Here we begin with a simple directory listing.

In the bad old days we needed to do something like this to produce a list of files in a directory.


<?php 
    $hdl 
opendir('./'); 
    while (
$dirEntry readdir($hdl))
    {
        if (
substr($dirEntry01) != '.')
        {
            if(!
is_file($dirEntry))
            {
                continue;
            }
            
$listing[] = $dirEntry;
        }
    }
    
closedir($hdl);
    foreach(
$listing as $my_file)
    {
        echo 
$my_file.'<br />';
    }
?>

Now consider a object oriented approach with SPL


<?php

    
try
    {
        
/*** class create new DirectoryIterator Object ***/
        
foreach ( new DirectoryIterator('./') as $Item )
        {
            echo 
$Item.'<br />';
        }
    }
    
/*** if an exception is thrown, catch it here ***/
    
catch(Exception $e)
    {
        echo 
'No files Found!<br />';
    }
?>

The code above will produce a simple list of all files and directories within a current working directory that is passed directly to the __construct(). This is all good, but we can do so much more. Lets create a file called foo.txt with some text in it. For windows users simply use notepad and for *nix users simply

echo 'This is a line of text' > foo.txt

Reflection

A full breakdown can be seen with the available methods from the DirectoryIterator by using the reflection API like this:


<?php
    Reflection
::export(new ReflectionClass('DirectoryIterator'));
?>

Now we have a full list of available methods to the DirectoryIterator class. The list will look something like this:

class [   class DirectoryIterator extends SplFileInfo implements Iterator, Traversable, SeekableIterator ] {
- Constants [0] {
}

- Static properties [0] {
}

- Static methods [0] {
}

- Properties [0] {
}

- Methods [35] {
Method [  public method __construct ] {
- Parameters [1] {
Parameter #0 [  $path ]
}
}

Method [  public method getFilename ] {
}

Method [  public method getBasename ] {
- Parameters [1] {
Parameter #0 [  $suffix ]
}
}

Method [  public method isDot ] {
}

Method [  public method rewind ] {
}

Method [  public method valid ] {
}

Method [  public method key ] {
}

Method [  public method current ] {
}

Method [  public method next ] {
}

Method [  public method seek ] {
- Parameters [1] {
Parameter #0 [  $position ]
}
}

Method [  public method __toString ] {
}

Method [  public method getPath ] {
}

Method [  public method getPathname ] {
}

Method [  public method getPerms ] {
}

Method [  public method getInode ] {
}

Method [  public method getSize ] {
}

Method [  public method getOwner ] {
}

Method [  public method getGroup ] {
}

Method [  public method getATime ] {
}

Method [  public method getMTime ] {
}

Method [  public method getCTime ] {
}

Method [  public method getType ] {
}

Method [  public method isWritable ] {
}

Method [  public method isReadable ] {
}

Method [  public method isExecutable ] {
}

Method [  public method isFile ] {
}

Method [  public method isDir ] {
}

Method [  public method isLink ] {
}

Method [  public method getLinkTarget ] {
}

Method [  public method getRealPath ] {
}

Method [  public method getFileInfo ] {
- Parameters [1] {
Parameter #0 [  $class_name ]
}
}

Method [  public method getPathInfo ] {
- Parameters [1] {
Parameter #0 [  $class_name ]
}
}

Method [  public method openFile ] {
- Parameters [3] {
Parameter #0 [  $open_mode ]
Parameter #1 [  $use_include_path ]
Parameter #2 [  $context ]
}
}

Method [  public method setFileClass ] {
- Parameters [1] {
Parameter #0 [  $class_name ]
}
}

Method [  public method setInfoClass ] {
- Parameters [1] {
Parameter #0 [  $class_name ]
}
}
}
}

Using the listed methods from the above code allows us to see how much information we can get from the class. The reflection API allows us to see inside the DirectoryIterator class so that each of the methods is displayed along with the class it inherits from. Lets go over it.

Exceptions

The constructor takes an directory path as its arguement. The directory must be a valid directory that is readable by the process. If the directory does not exist, or cannot be accessed, an exception is thrown as shown here.


<?php
    $it 
= new directoryIterator('does_not_exist');
?>

The code above will produce an fatal error due to an un-caught exception like this:

Fatal error:Uncaught exception 'UnexpectedValueException' with message 'DirectoryIterator::__construct(does_not_exist) [<a href='directoryiterator.--construct'>directoryiterator.--construct</a>]: failed to open dir: No such file or directory' in /www/it.php:2

Stack trace:

#0 /www/it.php(2): DirectoryIterator->__construct('does_not_exist')

#1 {main}

thrown in /www/it.php on line 2

Because of this, it is important to catch and handle any exceptions. Here a simple try/catch is used to handle the exception and display the error.


<?php
    
try
    {
        
/*** specify a non-existant directory ***/
        
$it = new directoryIterator('does_not_exist');
    }
    catch(
Exception $e)
    {
        
/*** echo the error message ***/
        
echo $e->getMessage();
    }
?>

Now that the exception has been caught, the error message is echo'ed in the catch block

DirectoryIterator::__construct(does_not_exist) [directoryiterator.--construct]: failed to open dir: No such file or directory

Of course, in a production environment, the exception would need to be handled in such a way as system error messages were not displayed to the user.

GetFilename

Moving on from the constructor are two very useful functions for use when iterating over a directory of files. These are

  • getFilename()
  • getBasename()

As their names suggest, these two class melthods are used to get the filename when iterating, and to get the basename. To get a feel for the return values of each, lets put them to work.

The getFilename method returns the name of the file in the current iteration.


<?php
    
try
    {
        
/*** freds home directory ***/
        
$it = new directoryIterator('/home/fred');
        while( 
$it->valid())
        {
            echo 
$it->getFilename().'<br />';
    
            
/*** move to the next element ***/
            
$it->next();
        }
    }
    catch(
Exception $e)
    {
        
/*** echo the error message ***/
        
echo $e->getMessage();
    }
?>

Freds home directory contains three files and the results look like this..

bar.php
.
my_stuff
.hidden.txt
..
foo.php
Note that even the hidden dot files and directories are displayed also when using the directoryIterator. This can be averted and will be shown in this section a little further on.

getBasename

The getBasename method add a little additional functionality to the getFilename method by adding an option arguement for a suffix. The suffix, when supplied, is removed from the returned values. This behaviour basically provides a filter for filenames without the additional overhead of creating filterIterator to further abstract the code.


<?php
    
try
    {
        
/*** freds home directory ***/
        
$it = new directoryIterator('/home/fred');
        while( 
$it->valid())
        {
            
/*** echo the file name, minus the suffix ***/
            
echo $it->getBasename('.php').'<br />';

            
/*** move to the next element ***/
            
$it->next();
        }
    }
    catch(
Exception $e)
    {
        
/*** echo the error message ***/
        
echo $e->getMessage();
    }
?>

The results from the above script show how the .php suffix has been stripped from filenames that end in .php.

bar
.
my_stuff
.hidden.txt
..
foo

The two methods above show how the getFilename and getBasename methods work with SPL. The also introduce some of the SPL directoryIterator methods for traversing an aggregate stucture, such as a directory listing. These include

  • rewind()
  • key()
  • current()
  • next()
  • valid()

isDot

Note also in the two above examples, all the files are displayed, including directories and even hidden files. Moving further into the list of methods displayed fromreflection, the idDot() and isDir() methods are provided to deal with exactly this situation.


<?php
    
try
    {
        
/*** freds home directory ***/
        
$it = new directoryIterator('/home/fred');
        while( 
$it->valid())
        {
            
/*** if if directory is a dot directory ***/
            
if( !$it->isDot() )
            {
                echo 
$it->current().'<br />';
            }
            
/*** move to the next element ***/
            
$it->next();
        }
    }
    catch(
Exception $e)
    {
        
/*** echo the error message ***/
        
echo $e->getMessage();
    }
?>
bar.php
my_stuff
.hidden.txt
foo.php

Now the the results change a little by the removal of the . and .. directories that point to the current directory and the parent directory. Note that the .hidden.txt file is still displayed. This is because the isDot() method only refers to the. and .. directories.

isDir

The isDir() method works in much the same fashion. This method returns true if the current iteration is a directory. Now it is simple to filter out the directories from the listing.


<?php
    
try
    {
        
/*** freds home directory ***/
        
$it = new directoryIterator('/home/fred');
        while( 
$it->valid())
        {
            
/*** if directory is a dot directory ***/
            
if( !$it->isDot() && !$it->isDir() )
            {
                echo 
$it->current().'<br />';
            }
            
/*** move to the next element ***/
            
$it->next();
        }
    }
    catch(
Exception $e)
    {
        
/*** echo the error message ***/
        
echo $e->getMessage();
    }
?>

With the isDir() method in place, the results now change to omit any sub directories that may be within the directory that is being iterated over.

bar.php
.hidden.txt
foo.php

Get File Last Access Time

Even further into the results from the reflection results, is a vast number of methods provided by the SPLFileInfo class which the directorIterator also extends from. Demonstrating each of these methods would be insane, but in these troubled times, who is to say that insane solutions are not the answer?!


<?php
    
try
    {
        
/*** freds home directory ***/
        
$it = new directoryIterator('/home/fred');
        while( 
$it->valid())
        {
            
/*** if directory is a dot directory ***/
            
if( !$it->isDot() )
            {
                echo 
$it->current().' '.date('Y m d H:i:s'$it->getATime() ). '<br />';
            }
            
/*** move to the next element ***/
            
$it->next();
        }
    }
    catch(
Exception $e)
    {
        
/*** echo the error message ***/
        
echo $e->getMessage();
    }
?>
bar.php 2008 11 21 18:47:35
my_stuff 2008 11 21 18:54:10
.hidden.txt 2008 11 21 18:48:19
foo.php 2008 11 21 18:47:32

Get Inode Modification Time


<?php
    
try
    {
        
/*** freds home directory ***/
        
$it = new directoryIterator('/home/fred');
        while( 
$it->valid())
        {
            
/*** if directory is a dot directory ***/
            
if( !$it->isDot() )
            {
                echo 
$it->current().' '.date('Y m d H:i:s'$it->getCTime() ). '<br />';
            }
            
/*** move to the next element ***/
            
$it->next();
        }
    }
    catch(
Exception $e)
    {
        
/*** echo the error message ***/
        
echo $e->getMessage();
    }
?>

Get Group


<?php
    
try
    {
        
/*** freds home directory ***/
        
$it = new directoryIterator('/home/fred');
        while( 
$it->valid())
        {
            
/*** if directory is a dot directory ***/
            
if( !$it->isDot() )
            {
                echo 
$it->current().' '.$it->getGroup(). '<br />';
            }
            
/*** move to the next element ***/
            
$it->next();
        }
    }
    catch(
Exception $e)
    {
        
/*** echo the error message ***/
        
echo $e->getMessage();
    }
?>
bar.php 500
my_stuff 500
.hidden.txt 500
foo.php 500

Get Inode


<?php
    
try
    {
        
/*** freds home directory ***/
        
$it = new directoryIterator('/home/fred');
        while( 
$it->valid())
        {
            
/*** if directory is a dot directory ***/
            
if( !$it->isDot() )
            {
                echo 
$it->current().' '.$it->getInode(). '<br />';
            }
            
/*** move to the next element ***/
            
$it->next();
        }
    }
    catch(
Exception $e)
    {
        
/*** echo the error message ***/
        
echo $e->getMessage();
    }
?>
bar.php 12976131
my_stuff 12976133
.hidden.txt 12976132
foo.php 12976130

Get Last Modification Time Of File


<?php
    
try
    {
        
/*** freds home directory ***/
        
$it = new directoryIterator('/home/fred');
        while( 
$it->valid())
        {
            
/*** if directory is a dot directory ***/
            
if( !$it->isDot() )
            {
                echo 
$it->current().' 'date('Y-m-d H:i:s'$it->getMTime() ). '<br />';
            }
            
/*** move to the next element ***/
            
$it->next();
        }
    }
    catch(
Exception $e)
    {
        
/*** echo the error message ***/
        
echo $e->getMessage();
    }
?>
bar.php 2008-11-01 18:47:35
my_stuff 2008-11-01 18:54:10
.hidden.txt 2008-11-01 18:48:19
foo.php 2008-11-01 18:47:32

Get File Owner


<?php
    
try
    {
        
/*** freds home directory ***/
        
$it = new directoryIterator('/home/fred');
        while( 
$it->valid())
        {
            
/*** if directory is a dot directory ***/
            
if( !$it->isDot() )
            {
                echo 
$it->current().' '$it->getOwner() . '<br />';
            }
            
/*** move to the next element ***/
            
$it->next();
        }
    }
    catch(
Exception $e)
    {
        
/*** echo the error message ***/
        
echo $e->getMessage();
    }
?>
r.php 500
my_stuff 500
.hidden.txt 500
foo.php 500

Get File Path


<?php
    
try
    {
        
/*** freds home directory ***/
        
$it = new directoryIterator('/home/fred');
        while( 
$it->valid())
        {
            
/*** if directory is a dot directory ***/
            
if( !$it->isDot() )
            {
                echo 
$it->getPath() .DIRECTORY_SEPARATOR$it->current().'<br />';
            }
            
/*** move to the next element ***/
            
$it->next();
        }
    }
    catch(
Exception $e)
    {
        
/*** echo the error message ***/
        
echo $e->getMessage();
    }
?>
/home/fred/bar.php
/home/fred/my_stuff
/home/fred/.hidden.txt
/home/fred/foo.php

Get Path Name

The above functions uses a joining of the path and the current filename within the iterator to fetch the file path and filename. The getPathName() method does this all in one go.


<?php
    
try
    {
        
/*** freds home directory ***/
        
$it = new directoryIterator('/home/fred');
        while( 
$it->valid())
        {
            
/*** if directory is a dot directory ***/
            
if( !$it->isDot() )
            {
                echo 
$it->getPathName().'<br />';
            }
            
/*** move to the next element ***/
            
$it->next();
        }
    }
    catch(
Exception $e)
    {
        
/*** echo the error message ***/
        
echo $e->getMessage();
    }
?>
/home/fred/bar.php
/home/fred/my_stuff
/home/fred/.hidden.txt
/home/fred/foo.php

Get File Permissions


<?php
    
try
    {
        
/*** freds home directory ***/
        
$it = new directoryIterator('/home/fred');
        while( 
$it->valid())
        {
            
/*** if directory is a dot directory ***/
            
if( !$it->isDot() )
            {
                echo 
$it->current().' '$it->getPerms().'<br />';
            }
            
/*** move to the next element ***/
            
$it->next();
        }
    }
    catch(
Exception $e)
    {
        
/*** echo the error message ***/
        
echo $e->getMessage();
    }
?>
bar.php 33188
my_stuff 16877
.hidden.txt 33188
foo.php 33188

Get File Size


<?php
    
try
    {
        
/*** freds home directory ***/
        
$it = new directoryIterator('/home/fred');
        while( 
$it->valid())
        {
            
/*** if directory is a dot directory ***/
            
if( !$it->isDot() )
            {
                echo 
$it->current().' '$it->getSize().'<br />';
            }
            
/*** move to the next element ***/
            
$it->next();
        }
    }
    catch(
Exception $e)
    {
        
/*** echo the error message ***/
        
echo $e->getMessage();
    }
?>
bar.php 23
my_stuff 4096
.hidden.txt 24
foo.php 21

Get File Type


<?php
    
try
    {
        
/*** freds home directory ***/
        
$it = new directoryIterator('/home/fred');
        while( 
$it->valid())
        {
            
/*** if directory is a dot directory ***/
            
if( !$it->isDot() )
            {
                echo 
$it->current().' '$it->getType().'<br />';
            }
            
/*** move to the next element ***/
            
$it->next();
        }
    }
    catch(
Exception $e)
    {
        
/*** echo the error message ***/
        
echo $e->getMessage();
    }
?>
bar.php file
my_stuff dir
.hidden.txt file
foo.php file

Is Executable


<?php
    
try
    {
        
/*** freds home directory ***/
        
$it = new directoryIterator('/home/fred');
        while( 
$it->valid())
        {
            
/*** if directory is a dot directory ***/
            
if( !$it->isDot() )
            {
                if( 
$it->isExecutable() )
                {
                    echo 
$it->current().' is executable.<br />';
                }
                else
                {
                    echo 
$it->current().' is not executable.<br />';
                }
            }
            
/*** move to the next element ***/
            
$it->next();
        }
    }
    catch(
Exception $e)
    {
        
/*** echo the error message ***/
        
echo $e->getMessage();
    }
?>
bar.php is not executable.
my_stuff is executable.
.hidden.txt is not executable.
foo.php is not executable.

Is File


<?php
    
try
    {
        
/*** freds home directory ***/
        
$it = new directoryIterator('/home/fred');
        while( 
$it->valid())
        {
            
/*** if member is afile ***/
            
if( $it->isFile() )
            {
                echo 
$it->current().'<br />';
            }
            
/*** move to the next element ***/
            
$it->next();
        }
    }
    catch(
Exception $e)
    {
        
/*** echo the error message ***/
        
echo $e->getMessage();
    }
?>
r.php
.hidden.txt
foo.php

Is Symbolic Link

For this example, a symbolic link has been created to the foo.php file. The symbolic link name is foo.phps to show the source code in correctly configured PHP installations.


<?php
    
try
    {
    
/*** freds home directory ***/
    
$it = new directoryIterator('/home/fred');
    while( 
$it->valid())
        {
            
/*** if directory is symbolic link ***/
            
if( $it->isLink() )
            {
                echo 
$it->current().'<br />';
            }
            
/*** move to the next element ***/
            
$it->next();
        }
    }
    catch(
Exception $e)
    {
        
/*** echo the error message ***/
        
echo $e->getMessage();
    }
?>
foo.phps

Is Readable


<?php
    
try
    {
        
/*** freds home directory ***/
        
$it = new directoryIterator('/home/fred');
        while( 
$it->valid())
        {
            
/*** check if element is a file ***/
            
if( $it->isFile() )
            {
                
/*** if directory is a readable ***/
                
if( $it->isReadable() )
                {
                    echo 
$it->current().'<br />';
                }
            }
            
/*** move to the next element ***/
            
$it->next();
        }
    }
    catch(
Exception $e)
    {
        
/*** echo the error message ***/
        
echo $e->getMessage();
    }
?>
bar.php
.hidden.txt
foo.phps
foo.php

Is Writable

Check If File Is Writable

For this demonstration, the bar.php file has been set to writable using

chmod a+w /home/fred/bar.php

<?php
    
try
    {
        
/*** freds home directory ***/
        
$it = new directoryIterator('/home/fred');
        while( 
$it->valid())
        {
            
/*** check if element is a file ***/
            
if( $it->isFile() )
            {
                
/*** if directory is a writable ***/
                
if( $it->isWritable() )
                {
                    echo 
$it->current().'<br />';
                }
            }
            
/*** move to the next element ***/
            
$it->next();
        }
    }
    catch(
Exception $e)
    {
        
/*** echo the error message ***/
        
echo $e->getMessage();
    }
?>
bar.php

__toString

__toString

The directoryIterator __toString() method is simply and alias of getPathname.


<?php
    
try
    {
        
/*** freds home directory ***/
        
$it = new directoryIterator('/home/fred');
        while( 
$it->valid())
        {
            
/*** check if element is a file ***/
            
if( $it->isFile() )
            {
                echo 
$it->__toString().'<br />';
            }
            
/*** move to the next element ***/
            
$it->next();
        }
    }
    catch(
Exception $e)
    {
        
/*** echo the error message ***/
        
echo $e->getMessage();
    }
?>
bar.php
.hidden.txt
foo.phps
foo.php

Seek

The directoryIterator class also extends SeekableIterator. This is a great plus as it permits the moving of the internal pointer to any place within the iteration.


<?php
    
try
    {
        
/*** freds home directory ***/
        
$it = new directoryIterator('/home/fred');

        
/*** seek to the second element in the structure ***/
        
$it->seek(2);
        while( 
$it->valid())
        {
            
/*** check if element is a file ***/
            
if( $it->isFile() )
            {
                echo 
$it->current().'<br />';
            }
            
/*** move to the next element ***/
            
$it->next();
        }
    }
    catch(
Exception $e)
    {
        
/*** echo the error message ***/
        
echo $e->getMessage();
    }
?>
.hidden.txt
foo.phps
foo.php

Rewind

As the name suggests, this method will rewind to the beginning of the iteration object.


<?php
    
try
    {
        
/*** freds home directory ***/
        
$it = new directoryIterator('/home/fred');
        while( 
$it->valid())
        {
            
/*** check if element is a file ***/
            
if( $it->isFile() )
            {
                echo 
$it->current().'<br />';
            }
            
/*** move to the next element ***/
            
$it->next();
        }

        
/*** rewind to the beginning of the iteration object ***/
        
$it->rewind();

        
/*** show the first member ***/
        
echo $it->__toString();
    }
    catch(
Exception $e)
    {
        
/*** echo the error message ***/
        
echo $e->getMessage();
    }
?>
bar.php
.hidden.txt
foo.phps
foo.php
bar.php

Get Link Target

Fetches the target path and filename of a symbolic link


<?php
    
try
    {
        
/*** freds home directory ***/
        
$it = new directoryIterator('/home/fred');
        while( 
$it->valid())
        {
            
/*** check if element is a file ***/
            
if( $it->isLink() )
            {
                echo 
$it->getLinkTarget().'<br />';
            }
            
/*** move to the next element ***/
            
$it->next();
        }
    }
    catch(
Exception $e)
    {
        
/*** echo the error message ***/
        
echo $e->getMessage();
    }
?>
/home/fred/foo.php

Get Real Path


<?php
    
try
    {
        
/*** freds home directory ***/
        
$it = new directoryIterator('/home/fred');
        while( 
$it->valid())
        {
            
/*** check if element is a file ***/
            
if( $it->isFile() )
            {
                
/*** fetche the real path ***/
                
echo $it->getRealPath().'<br />';
            }
            
/*** move to the next element ***/
            
$it->next();
        }
    }
    catch(
Exception $e)
    {
        
/*** echo the error message ***/
        
echo $e->getMessage();
    }
?>

Summary

This tutorial has shown how dealin with directory structures is made more efficient within an Object Oriented framework. SPL iterators provide this same functionality for all aggregate structures, regardless of type.

Credits

Many thanks to Marcus Boerger for his help with the code in this tutorial and quick fixes to bugs in the PHP 5.1.0 core as we came across them. Marcus is a PHP core developer and the creator of SPL. He has provided excellent docs that show the inner workings of SPL at http://www.php.net/~helly/php/ext/spl/.