PHP: Directories and File Management


Dealing with Files and Folders

One of the more complex aspects of web design is dealing with files, dealing with folders, and so on. On this page I intend to show some of what I've been doing to deal with these issues.

The Root Folder

The first problem is that most websites can easily get pretty complicated, and most web developers prefer to keep files in specific locations to remain organized, having folders for images, their cascading stylesheets, javascript code, and more. The problem then becomes navigating all that in your code.

In my PHP applications, at the top of every page, there is a small routine based on a much more complex set of code created by Ronnie MacGregor (an old friend I met in the dBASE communities, who these days is a web developer for his company in Scotland). The code returns the root level of the website, and can be called from anywhere and always return the root:

            // determine top level of site, code based on work by Ronnie MacGregor:
            $sRootPath = "";
            $sDocPath = substr($_SERVER['SCRIPT_FILENAME'], strlen($_SERVER['DOCUMENT_ROOT']));
            for($nCount = 0; $nCount < (substr_count($sDocPath, '/') -2 ); $nCount ++)
            {
               $sRootPath .= "../";
            }
         

If you are four levels down, the root level will be: "../../../" and so on. The purpose of this is to always know where you are creating files, where things are in relationship to your current folder, etc. (If you need an image, and it is in the folder root/images, being able to use this variable to reference the root makes things so much easier ...).

The Current Filename

While searching the web to find an efficent method of determining the current filename being executed, I stumbled across a really simple means of getting this information (some code was really complex). This works great, and is useful for a lot of situations:

            // current filename without path:
            $sFileName = basename( $_SERVER['SCRIPT_FILENAME'] );
         

For example, in a lot of places I need to do things based on which file is being executed at that time. I can us a simple PHP if statement:

            if( $sFileName == "directories.php" )
            {
               // do whatever I need here ...
            }
         

Folder Management

When building my CMS, I decided I wanted the app to do the processing of storing files (photos, images, etc.) in ways that made it easy to keep track of, and to handle cleanup if some content were deleted.

PHP has a couple of functions that help with this. The first is to create a directory, the second is to remove a directory.

Creating Directories (Folders)

To create a directory the function at it's simplest is:

            mkdir( "/some/folder" );
         

However, while digging around in the book PHP and MySQL Web Development1, I came across some information about permissions. The script (your PHP program) has to have access. The function mkdir() has two parameters, the first is the path where you need to create the folder, but the other is a permission setting. This gets trickier, because there is a "umask" (I am not sure what that means even after digging around in the online PHP manual) that can offset the permissions.

The authors suggest that you set the umask temporarily to 0, then create the folder, and reset the umask. This can be done relatively simply:

            $oldumask = umask(0); // Save old setting and set umask to zero
            $mkdir( "myFolder", 0777 );
            $umask( $oldumask ); // reset
         

The authors also note that if you are testing your app on a Windows computer the umask function will have no effect. But on the Linux servers that most websites are hosted on this can be a big deal.

When a new content item is created, my code creates folders for images, for "other files", etc. (Conversely, if the content gets deleted the folders are emptied out and removed ... see below.)

            // determine path to create -- content_id comes from a field in the table:
            $uploadPath = $sRootPath . 'content/images/in_content' . $content_id;
            // if the path does not exist
            if( ! file_exists( $uploadPath ) )
            {
               // create it:
               $oldumask = umask(0); // deal with potential offsets
               if( ! mkdir( $uploadPath, 0777 ) )
               {
                  // we had a problem:
                  echo "We had a problem creating the folder: " . $uploadPath . "<br /<";
                  exit;
               } // endif ! mkdir
               umask( $oldumask ); // reset
            } // endif ! file_exists
         

I also create other folders, but that should give you the idea.

Removing a Directory (or Folder)

Removing a directory, if it is empty, is pretty easy:

            rmdir( "myfolder" );
         

The trick is that it has to be empty. The example below uses the same folder structure as above, when deleting a content item -- read the comments -- this uses the list_images() function which is described below:

            $path = $sRootPath . 'content/images/in_content' . $content_id;
            // call function to get a list of images in the folder -- listed below:
            $file_list = list_images( $path );
            for ($i = 0; $i< count( $file_list ); $i++ )
            {
               if( file_exists( $path . "/" . $file_list[$i] ) )
               {
                  // unlink is the delete command, realpath() is a PHP function
                  // that seems to be important in evaluating the unlink -- without
                  // it, on a Windows system when testing, I got errors, with it
                  // the unlink function works perfectly:
                  if ( unlink ( realpath( $path . "/" . $file_list[$i]  ) ) )
                  {
                     // done
                  } // unlink
               } // file exists
            } // end for loop

            // folder should be empty, delete it:
            if( ! rmdir( $path ) )
            {
               // note that there was a problem, the folder couldn't be deleted
               // add Javascript alert "Success ..." ...
               echo "<script>alert('Problem deleting folder [" . $path . "]'! (It is probably not empty.)')</script>";
            }
         

List All Files in a Folder

I found I needed a way to get a listing of all the files in the folder, so I could delete them. This isn't too complex, but it is useful. I specifically created it for a list of images, but you could change the file extensions easily enough. I called this "list_images()", because I was specifically interested in getting any images in a folder. You could simply remove the need to check file extensions to make it list all the contents of a folder. This is in my library of PHP functions that is just included in all files:

            /*
               list_images will return an array of the image files in
               a folder on the server. You must pass a folder with
               full path (i.e., for my own apps, $sRootDir . 'somepath/anotherfolder' )
               This code was put into a function by Ken Mayer, but posted
               on StackOverflow by Madan Sapkota, on this page:
               http://stackoverflow.com/questions/3226519/how-to-get-only-images-using-scandir-in-php
            */
            function list_images( $folder )
            {
               // image extensions
               $extensions = array('jpg', 'jpeg', 'png', 'gif', 'bmp', 'svg', '.ico' );
               
               // init result
               $result = array();
               
               // directory to scan
               $directory = new DirectoryIterator( $folder );
               
               // iterate
               foreach ($directory as $fileinfo)
               {
                   // must be a file
                   if ($fileinfo->isFile())
                   {
                       // file extension
                       $extension = strtolower(pathinfo($fileinfo->getFilename(), PATHINFO_EXTENSION));
                       // check if extension match
                       if (in_array($extension, $extensions))
                       {
                           // add to result
                           $result[] = $fileinfo->getFilename();
                       } // endif in_array
                   } // endif fileinfo->isfile()
               } // endfor
               
               return $result;
            } // eof: list_images()
         

Uploading Files

Uploading files using the web and PHP and so on can be a bit tricky at times, and it took me a while to come up with something I was happy with. Of course, I am sure there are plenty of other ways of doing this. There are multiple things to be aware of if you wish to allow your user to upload files to the server.

  1. The HTML form Tag
  2. The HTML input Tag
  3. The PHP code to process the file upload

The HTML form Tag

The first item that messes me up on a regular basis is forgetting to add an attribute to the form tag. A standard HTML form tag might look like:

            <form class="form-horizontal" method="post" action="">
         

Depending on how your pages are set up, the action attribute may have something in there, and may not. I tend to process everything in the same PHP file, so there is no need for it, but that's a philosophical thing about coding, and I don't want to get into it here.

The problem is that if you want to allow the user to upload a file (or multiple files), you need to modify this tag and add a new attribute:

            <form class="form-horizontal" method="post" enctype="multipart/form-data" action = "">
         

The enctype attribute, specifically using "multipart/form-data" as the value, is what allows processing of file uploads.

The HTML input Tag

For file uploads, the input tag in HTML has a special option:

            <label for="image_gallery">Upload Image File:</label>
            <input name="my_image" id="my_image" type="file" />
         

This would give something like the form below (pretty basic form, placed in a Bootstrap well to make it stand out just a little):

For this example there is no code that goes with this to process the upload, so while you could open the file dialog and select a file, that's as far as anything would go (so you can't actually upload files to my server). Now, if you wanted to upload multiple files at one time, there are a couple of changes to the way the HTML tag would look:

            <label for="image_gallery">Upload Image File:</label>
            <input name="my_image[]" id="my_image" type="file" multiple />
         

The first is not very obvious -- the name attribute uses square brackets to note this is an array (my_image[]), and then we add the word "multiple" at the end.

The difference between the two is that the first will only accept one file for uploading, the second will accept multiple files -- the user needs to hold the Control (or Command on a Mac) key while selecting files with their mouse.

The PHP Code to Upload the File

The code shown below is the code I use to process an image upload. I have different code that deals with processing multiple files, which I will make available as a download ...

            /*
               -------------------------------------------------------------------------------------------
               fileUpload() -- based on a version from:
                  http://www.tidy-designs.co.uk/website-development/php-secure-file-upload-script/
               Simplified a little, but it still does the validation required.
               
               call:
               fileUpload( [filename], [uploadpath], [newFileName] )
               // remember: filename must be a reference to the
               // name of the file input object in the form
               // in other words:
               fileUpload( 'armory', $armory_path, "someNewImage" );
               
               NOTES: uploadPath must have an ending /
                      newFileName should *not* have an extension
               -------------------------------------------------------------------------------------------
            */
            function fileUpload( $filename = null, $uploadpath = null, $newFileName = null )
            {
               //Set default file extension whitelist
               $whitelist_ext = array('jpg', 'jpeg', 'png','gif','bmp', 'ico');
               //Set default file type whitelist
               $whitelist_type = array('image/jpeg', 'image/png','image/gif', 'image/bmp', 'image/x-icon' );
               $max_size = 5000000; // 5MB
               $error_message = "";
            
               if( $_FILES[$filename]['size'] > $max_size )
               {
                  $error_message .= "File size should not exceed 5MB <br />";
               }
            
               $file_info = pathinfo($_FILES[$filename]['name']);
               $name = $file_info['filename'];
               $ext = $file_info['extension'];
            
               //Check file has the right extension
               if (!in_array($ext, $whitelist_ext))
               {
                  $error_message .= "Invalid file Extension <br />";
               }
               
               //Check that the file is of the right type
               if (!in_array($_FILES[$filename]['type'], $whitelist_type))
               {
                  $error_message .= "Invalid file Type <br />";
               }
               
               // this works for images only, looks for image size
               // data, if it is not there, it's not an image at all!
               if (!getimagesize($_FILES[$filename]['tmp_name']))
               {
                  $error_message .= "Uploaded file is not a valid image <br />";
               }
               
               // everything checks out:
               if ( $newFileName == "" )
               {
                  $newFileName = $name;
               }
               // add the file extension to the filename:
               $newFileName .= "." . strtolower( $ext );
               // replace spaces with underscores:
               $newFileName = str_replace ( " ", "_", $newFileName );
               
               // move from temp location to new location with new filename:
               if ( move_uploaded_file($_FILES[$filename]['tmp_name'], $uploadpath.$newFileName ))
               {
                  $error_message = ""; // no error
               }
               else
               {
                  $error_message .= "File didn't upload ...";
               }
               return $error_message;
            } // eof: fileUpload()
         

For the code, I added the ability to output the file to a new filename from the original, which allows the coder to do something to obfuscate the filename, to make sure that the file uploaded has a unique filename, that kind of thing.

The PHP code to handle multiple file uploads would need to wrap a lot of the code above inside a loop and process the array. I am not going to post all that here.

As an example, this is something I found in a discussion on Stack Overflow that I use with profile images for a user, as a user might change their profile image every so often:

            // new name -- username+"_profile_"+random number based on time
            $newFileName = $user_name . "_profile_" . round(microtime(true));
         

File Upload Part 2

Of course, tying it all together you have to have some code somewhere that reads the data from the input tag. I tend to use post as the method for submitting forms, but you can't use $_POST with a file. Instead you have to use the PHP $_FILES object:

            $my_image = $_FILES['my_image']['name'];
         

And then you would need to process it. This would be done by determining where to upload the file to, then use the PHP function fileUpload() which is defined above (I keep this in a separate file and include in any page that needs it):

            $my_image = $_FILES['my_image']['name'];
            // new name -- username+"_profile_"+random number based on time
            $newFileName = $user_name . "_profile_" . round(microtime(true));
            // get the file extension -- I was needing this at one point, but don't seem to now,
            // have left it here as something you might find useful:
            //$ext = strtolower( pathinfo($_FILES['my_image']['name'], PATHINFO_EXTENSION) );
          
            // call function:
            $uploadPath = $sRootPath . 'profileimages/';
            $error = fileUpload( 'profile_image', $uploadPath , $newFileName );
            if( $error == "" ) // if empty error message
            {
               // no error, is there anything you need to do?
            }
            else
            {
               $error .= "<b>Profile image didn't upload ...</b>";
               echo "<div class='alert alert-danger' role='alert'>".$error."</div>";
            } // endif error=""
         

The error display relies on the Bootstrap alert, if not using Bootstrap, you might want to do something else.

Note that this code does not check to see if the file already exists. By default PHP will simply overwrite a file with the same filename. I have chosen to not worry about it with my own CMS, due to the way I am storing the data. The following are the reasons:

File Upload Routines

If you are interested in getting the code I have created for file uploads, you can download it here. There are a lot of notes in there, including discussion of mime types used to validate the type of file(s) being uploaded: [fileUpload.php].

File Downloads

If you want your users to be able to download files, it turns out this is a very easy thing to do. Using the html anchor tag, there is a simple keyword you add to the tag to allow for downloads. The example below is the tag used above to download the file "fileUpload.php", and it shows using the PHP variable for the root of the site:

            <a href="<?php echo $sRootPath; ?>downloads/fileUpload.php" download>fileUpload.php</a>
         

That's About It ...

That's about all I can throw at you here. File management is never easy, but hopefully this helps.


1 PHP and MySQL Web Development, 4th Ed., Welling, Luke and Laura Thomson, Oct. 2013, Pearson Education, Inc., ISBN: 978-0-672-32016-6