Tuesday, May 14, 2013

File and Folder Iterator in PHP


Problem  : We want to show all files and folders in the curent folder including subdirectories and their content.
Solution : We would be solving this issue using 3 methods as listed below.

a. Using Directories extension
b. Glob method
c. RecursiveDirectoryIterator method

a. The "Directory" extension consists of various useful functions like opendir(), readdir(), closedir(), scandir() etc. Let's use them.

We have a directory stucture as shown in the picture. We would be traversing through each folder recursively and get their respective contents and show them on screen.



Before we fully develop the full solution, here let's write a small program for showing content of current folder. Check the code below.

<?php
print_r( scandir("dir1",0) );
?>


Scandir simply shows all files and folder listings inside the specified path. The output is ..

Array
(
    [0] => .
    [1] => ..
    [2] => 1a.txt
    [3] => 1b.txt
    [4] => dir2
)

The output quite matches the output of DOS "dir" command where single dot "." means current folder and two dots ".." refer to parent folder. The second argument is given for sorting order.. 0 is for ascending order while 1 is for descending order. The folder "dir2" resides inside "dir1" folder and we can get inside "dir2" by implementing one recursive algorithm as shown below...

<?php
// Define a folder to scan
$root_folder = "dir1";

// Call the function for folder browsing
browse_folder( $root_folder );

// Function Definition
function browse_folder( $root_folder )
{
  // Scan the specified path
  $tree = scandir( $root_folder ,  0 );

  // Print Current folder Tree
  echo "<br> Showing content of $root_folder ::<br>";
  print_r($tree);

  // Check if any subfolder exists
  // Ignoring 0th and 1st index
  for($i=2;$i<count($tree);$i++)
  {
    // Check each item using is_dir() for sub-folder checking
    if( is_dir( $root_folder . "/" . $tree[$i] ) )
    {
       // Get into that sub-folder and show content
       // Notice, that this is a recursive call
       browse_folder( $root_folder . "/" . $tree[$i] );
    }
  }

}
?>


The above code traverses all subfolders of specified path and displays all contents inside. If any subfolder is found, then that folder is also browsed and its contents are displayed. We use a recursive call here. All the folders' real path has to be provided to the browse_folder function. That's why we had to use a "/" in the function call ::

browse_folder( $root_folder . "/" . $tree[$i] );

which actually builds the corect path to any subfolder found with respect to the current working directory which in my case is "htdocs". To get your current working directory, write this:

<?php
echo getcwd();
// In my case, it shows C:\xampp\htdocs

// change the current folder to something else
chdir("dir1/dir2");

echo getcwd();
// In my case, it shows C:\xampp\htdocs\dir1\dir2
?>


Another function in Diectories extension is "dir()" which opens a directory and then through read() method we iterate through each item inside.

<?php
// Define a folder to scan
$root_folder = "dir1";

// Open a directory
$fp = dir( $root_folder );

// List files in it
while ( ($file = $fp->read()) !== false)
{
  echo "File: " . $file . "";
  echo is_dir( $root_folder . "/" . $file) ? "[dir]" : "[file]" ;
  echo "<br/>";
}

// Close the directory
$fp->close();
?>


The dir() method creates an instance of Directory class with two member variable namely "path" and "handle". The above code would generate a list of all file and folders inside the directory specified.

Let's solve this folder browsing problem once again using opendir() and readdir() methods. Check the code below.

<?php
// Define a folder to scan
$root_folder = "dir1";

// Call the function for folder browsing
browse_folder( $root_folder );

// Function Definition
function browse_folder( $root_folder )
{
   // Check if it a dir
   if( is_dir( $root_folder ) )
   {
     // TRY to open a handle for the dir
     $fp = opendir($root_folder);
    
     // dir opening problem may occur due to file
     // pemission or other file system error
     if($fp !== false )
     {
        echo "<br> Showing content of [$root_folder] ....";
       
        // Read next entry from directory handle
        // in the order in which they are stored
        // in the fileSystem
       while ( ($file = readdir($fp))  !== false)
       {
         // File or Folder with Relative Path

         $fi_le = $root_folder . "/" . $file ;

         // Display file/folder name etc
         echo "<br> filename: [$file], filetype: [" . filetype(
$fi_le ) . "]" ;
   
         // If it is a Sub folder, then again get inside it
        if( $file!= "."  && $file!=".." )
        {
         if( filetype(
$fi_le ) == "dir" )
         {
           // Make the recursive call
           browse_folder(
$fi_le );
         }
        }
       } // While closes
      } // If closes

     // Close the directory Handle
     closedir($fp);
   }
}
?>


The above code would beautifully display all your folder contents. Check the output below :



Couple of points to be noted here ..
i. opendir() should be accompanied by a respective closedir() method which closes the directory handle opened by opendir()
ii. We use filetype() function which returns "dir"/"file" if the supplied item is a "directory"/"file" respectively.
iii. We ignored the "." and ".." folders
iv. readdir() function reads next item within the specified directory and retuns "FALSE" if anything goes wrong. In my case, files are displayed ahead of folders which is why I could avoid the pain in displaying the contents. Say, if readdir() reads a folder content as shown below...

file1.txt [file]
file2.txt [file]
folder1   [dir]
file3.txt [file]
folder2   [dir]
file4.txt [file]


In this case the above code would first print "file1.txt" and "file2.txt" and then it recursively calls the browse_folder() function to show contents of "folder1", then again "file3.txt" would be displayed and next contents of "folder2" would be displayed and so on... This way the output may become totally unordered. This problem may arise because we are using a single loop for displaying items and browsing sub-folders. However this problem can always be overcome by the following logic.. Just using two separate loops for displaying and subfolder-browsing.

i) Display all contents of current folder using readdir() in a loop
ii) Rewind the directory handle by using rewinddir($fp) statement
iii) Again start a loop using readdir() to see if any folder is sub-folder. If a sub-folder is found, then call the browse_folder() method

Glob method is discussed in article Using Glob in PHP

DirectoryIterator methods are discussed in article DirectoryIterator in PHP

No comments: