What's new in PHP V5.2, Part 5: Tracking file upload progress

Create a real-time progress bar with $_POST array and APC_UPLOAD_PROGRESS

PHP V5.2 added hooks for developers to take advantage of tracking file upload progress in real time. This article, Part 5 of a five-part "What's new in PHP V5.2" series, shows how to monitor file uploads and write code accordingly, with the creation of a PHP progress bar.

Share:

Tracy Peterson (tracy@tracypeterson.com), Freelance Writer, Freelance Developer

Tracy Peterson has worked as an IT project manager and Web developer since 1997 and most recently worked as an operations program manager on MSN Search at Microsoft. He is based in San Francisco.



15 May 2007

Also available in Chinese Japanese

Web 2.0 is the hottest buzzword on the Internet, with investors lining up to put money into any bucket labeled by it. There are many descriptive terms for the millions of Web sites and applications they house. With Web 2.0, we describe a category of Web sites that provide a voice to the millions of users of the Internet. Distinctive in that they all provide a venue for users to meet and share opinions and data relating to common interests, these sites generate enormous amounts of content rapidly.

Each of the users provides some kind of content -- reviews of coffee shops, routes to work, etc. YouTube is a great example of this, providing a place for people to upload videos and have other users watch them and provide feedback. YouTube is the current darling of the Web 2.0 observers, noting that YouTube's popularity grew faster than any site on the Internet to date. This popularity can be attributed to a great deal of varied content, along with the ability for users to lend their voices to the content in the form of comments. And not just comments -- users can even upload video comments in response to video.

Text fields, ahoy

Many Web sites that accept files sport the dreaded Browse button next to a text field urging users to upload each file one at a time. This can take a long time, especially in the case of video, or even photos or other items that come in a groups of smaller files. Since each file requires its own upload, it can be quite a chore. Given that uploading huge files can be tedious for impatient users, it is important to provide them positive feedback to keep them from giving up and going away.

Fortunately, PHP V5.2's new hooks into the file upload process allow us to show users in real time what is happening with their uploads. In this article, we will create a progress bar using PHP V5.2 for our users (see Download for source code).


Hook, line, and sinker

The new "hooks" in PHP V5.2 are actually data points that are available during the file transfer process if you have the right libraries installed and configured. They use a feature called the Alternative PHP Cache. When a PHP script receives an uploaded file, the interpreter will automatically check the $_POST array for a hidden field named APC_UPLOAD_PROGRESS, which becomes a cached variable, storing information about the upload so your scripts can access it. With this information cached and at your fingertips, you can give your users visual feedback to improve their user experience.

We will cover the implementation of the APC code in your HTML form, as well as identifying it in your PHP and how to access the cached information. There are many ways to represent this data — from Ajax to FLEX — but what we will focus on is how to prepare access to the data these front-end technologies will need.


Setting up

APC is not enabled by default in PHP V5.2. Since the new hooks are a part of APC, we need to make sure to install the extension and make it available to the PHP interpreter. This is accomplished by downloading the php_apc extension files. In our case, we are using an installation of WAMP, a freely available packaged PHP for Windows®, which includes Apache and MySQL. It offers a nice user interface and is easy to manage with menus that support configuration options.

To set up APC on WAMP:

  1. See Resources to download the libraries and WAMP.
  2. Install WAMP.
  3. Put the php_apc.dll file in the extensions folder for PHP. This is <wamproot>/php/ext by default.
  4. Use the system tray WAMP menu to select PHP settings>PHP Extensions>Add Extension.
  5. In the command-line interface that pops up, type php_apc.dll and press Enter.
  6. Using a text editor, open <wamproot>/php/php.ini and add the line apc.rfc1867 = on (it doesn't matter where). If you're trying to test locally and plan to upload large files so you can actually see progress, you'll also want to add the following directives: apc.max_file_size = 200M, upload_max_filesize = 200M, and post_max_size = 200M. Don't do this on a live production server, though, or you're likely to use up bandwidth and disk space allotments, not to mention slowing everyone else down to a crawl.
  7. Restart PHP.

APC should now be set up and initialized. The RFC1867 features of APC — the features that enable you to track file uploads — should now be enabled as an option, and you should be ready to look into our file uploads to enable real-time status.


Accounts receivable

To receive a file, we must first set up a form which will receive the file. Conveniently, HTML comes with a standard field type for files. Like all HTML form fields, it is named logically, as type file. It comes with a handy Browse button that appears to the right of the block by default.

Listing 1. HTML form for upload.php
<?php
   $id = $_GET['id'];
?>

<form enctype="multipart/form-data" id="upload_form" 
      action="target.php" method="POST">

<input type="hidden" name="APC_UPLOAD_PROGRESS" 
       id="progress_key"  value="<?php echo $id?>"/>

<input type="file" id="test_file" name="test_file"/><br/>

<input onclick="window.parent.startProgress(); return true;"
 type="submit" value="Upload!"/>

</form>

We need to make a PHP page for this form because we need a unique key to track the upload. Ultimately, it will be part of the URL used to call this page as a GET value. This number is going to be the value for the APC cache entry key we will retrieve later. To pass that value, the form field needs a hidden field with a special name that will let APC know that it needs to store the file upload status. This field is called APC_UPLOAD_PROGRESS. This is the aforementioned hook that starts the caching process. To make sure PHP can access the correct item in the cache, we use the unique ID we retrieved as the value of the hidden field, thus creating a key of that value. Once the user submits the form -- we'll deal with the submit button shortly -- the browser sends the file and the key as part of the POST data sent to the server.

Loaded in the browser, this page should provide a very simple form, as you can see in Figure 1.

Figure 1. The upload form
The upload form

To enable the user to submit the file without reloading the entire page, you're going to embed this form in an iframe within another file. If you tried to use only the form action page (target.php) to retrieve the data, you wouldn't be able to see any cache information because the page won't return any information until the upload is complete. For this reason, the most popular examples of use for this new hook have been written in Ajax. That way, you can submit the form and still continue to check the status of the upload, in the same window without refreshing.

To make our script work, you need to move on to a containing page, which will set up the iframe and receive the uploaded file information. You also need a set of JavaScript functions to get the data for the progress indicator and to display the progress indicator.


Catching the throw

When the file is included in the submitted form, it is sent to a temporary location on the server until it is saved to a permanent location. When it is in the temporary storage, it is available through the $_FILES associative array. Using the file upload functions that are standard issue with PHP, you can select a path and save them to the server or handle them however you want.

Listing 2. The target.php file
<?php  

if($_SERVER['REQUEST_METHOD']=='POST') {
  move_uploaded_file($_FILES["test_file"]["tmp_name"], 
  "c:\\sw\\wamp\\www\\" . $_FILES["test_file"]["name"]);
  echo "<p>File uploaded.  Thank you!</p>";
}

?>

First, check to see if the POST variables, which come from our form, have been set and indicate we have received the form data. If we receive the form data, and hopefully a file, we should also have a global array $_FILES. We move the uploaded file to a safe place, depending on what we want to do with it. In this case, we simply move the file to \sw\wamp\www. (This is, of course, a completely arbitrary location. Feel free to choose one of your own.) Once we complete that action, we thank the user.

We include the actual file handling here mostly for completeness. Because this article is about the progress bar, it doesn't really matter what you do with the actual file when you receive it.


Making progress

You're also going to need a script that returns the actual upload progress. Listing 3 shows a very simple version.

Listing 3. The getprogress.php file
<?php
if(isset($_GET['progress_key'])) {

  $status = apc_fetch('upload_'.$_GET['progress_key']);
  echo $status['current']/$status['total']*100;

}
?>

This script first looks for the progress_key, the $id value we were discussing earlier. (Don't worry, you'll see where it comes from in a moment.) This leads to the call to apc_fetch(), which returns the data from the APC cache. We need the correct file information, so we need the unique ID, represented here as $_GET['progress_key']. We call apc_fetch() with the parameter upload_xxxxxx where xxxxxx is the unique ID; PHP automatically prepends the upload_ part.

Once we get the data, you can use the JSON extension to format the information into a format more convenient to use in JavaScript and return the whole object, if you like. The $status object is an array with the following fields:

total
The total size of the file
current
The amount of the file received so far
rate
The upload speed in bytes per second
filename
The name of the file
name
The name of the variable
temp_filename
Where PHP is saving that temporary copy of the file
cancel_upload
Whether the upload has been cancelled (1) or not (0)
done
Whether the upload is complete (1) or not (0)

In this case, you need only the percentage complete. You may choose to use more information in your own applications.


JavaScript shows the bar

Now you're ready to start building the actual progress bar. To keep things simple, the script uses CSS to create a div that emulates a bar and can be controlled using JavaScript, as in Listing 4.

Listing 4. The main file, progress.php
<html>
<head><title>Upload Example</title></head>
<body>

<script type="text/javascript">

var counter = 0;

function startProgress(){
    document.getElementById("progressouter").style.display="block";
    fire();
}

function fire(){
   if (counter < 101){
     document.getElementById("progressinner").style.width =
                                                     counter+"%";
     counter++;
     setTimeout("fire()",100);
   }
}

</script>

<div id="progressouter" style=
    "width: 500px; height: 20px; border: 6px solid red; display:none;">
   <div id="progressinner" style=
       "position: relative; height: 20px; background-color: purple; width: 0%; ">
   </div>
</div>

<span onclick="startProgress()">Start me up!</span>

</body>
</html>

This page includes one div element inside another that serves as a border. The script will resize the inner div relative to the border to show progress. When the user clicks the Start me up! text, the startProgress() script calls the fire() function. That function checks the value of the counter, and if it's not yet past 100, it sets the inner div to that percentage of the outer div's width. It then increments the counter and tells the browser to do it all again in one-tenth of a second.

The result looks something like Figure 2.

Figure 2. The progress bar script
The progress bar script

Now you just need a way to get the script to update the width not with an arbitrary number but with the completion percentage.


Putting it all together

All you've got left to do now is to hook everything together. You can do that with the progress.php page.

Listing 5. The final progress.php page
<?php
   $id = uniqid("");
?>
<html>
<head><title>Upload Example</title></head>
<body>

<script src="http://maps.google.com/maps?file=api&v=2&key=<yourkeyhere>"
            type="text/javascript"></script>

<script type="text/javascript">

function getProgress(){
  GDownloadUrl("getprogress.php?progress_key=<?php echo($id)?>", 
               function(percent, responseCode) {
                   document.getElementById("progressinner").style.width = percent+"%";
                   if (percent < 100){
                        setTimeout("getProgress()", 100);
                   }
               });

}

function startProgress(){
    document.getElementById("progressouter").style.display="block";
    setTimeout("getProgress()", 1000);
}

</script>

<iframe id="theframe" name="theframe" 
        src="upload.php?id=<?php echo($id) ?>" 
        style="border: none; height: 100px; width: 400px;" > 
</iframe>
<br/><br/>

<div id="progressouter" style=
   "width: 500px; height: 20px; border: 6px solid red; display:none;">
   <div id="progressinner" style=
       "position: relative; height: 20px; background-color: purple; width: 0%; ">
   </div>
</div>

</body>
</html>

Starting at the bottom and working our way up, we've added an iframe that embeds the upload.php script from Listing 1, feeding it the unique ID generated at the top of the page.

Now, remember the Submit button from that form?

<input onclick="window.parent.startProgress(); return true;"
 type="submit" value="Upload!"/>

That button does two things. It does submit the form, like a normal Submit button, but before it does that, it calls the startProgress() script in the main window. The startProgress() script tells the progress bar to display itself — it starts out with a display property of none —, then tells the browser to wait for one second before executing the getProgress() script.

Now, the getProgress() script is where things get interesting. Remember early on, I said that we will need to use Ajax or some similar method to check on the file's progress? Well, in this case, the form uses a shortcut, calling the GdownloadUrl() function from the Google Maps API library. (Notice that the form imports the library at the top of the page. You'll need to obtain your own key for this library, but it's free from Google.)

This function downloads the content of a URL — in this case, the getprogress.php script — and executes the anonymous function defined there. The first parameter accepted by the function is the data returned from the URL, which, in this case, is the percentage, so use it to update the progress bar. Finally, if the file isn't done downloading yet, tell the browser to try again in one-tenth of a second. (In reality, you probably won't be able to execute these calls that fast, but the browser will do what it can.)

The end result is a page that enables the user to see the progress of files being uploaded.

Figure 3. Output from Progress.php
Output from Progress.php

Summary

In the Web 2.0 world, we encourage our users to provide information and content for each other on our Web sites. As developers, we create a framework for this open and free exchange of data from person to person. While the tools that make this possible have been in place for a long time, the user experience hasn't been the best it could have been. In this article, you have seen some ways to provide real-time feedback to the users in order to improve that user experience and the quality of our applications, specifically by providing a progress bar for the information they upload to the site.


Download

DescriptionNameSize
Sample jar filesos-php-v525.source.zip2.5KB

Resources

Learn

Get products and technologies

Discuss

Comments

developerWorks: Sign in

Required fields are indicated with an asterisk (*).


Need an IBM ID?
Forgot your IBM ID?


Forgot your password?
Change your password

By clicking Submit, you agree to the developerWorks terms of use.

 


The first time you sign into developerWorks, a profile is created for you. Information in your profile (your name, country/region, and company name) is displayed to the public and will accompany any content you post, unless you opt to hide your company name. You may update your IBM account at any time.

All information submitted is secure.

Choose your display name



The first time you sign in to developerWorks, a profile is created for you, so you need to choose a display name. Your display name accompanies the content you post on developerWorks.

Please choose a display name between 3-31 characters. Your display name must be unique in the developerWorks community and should not be your email address for privacy reasons.

Required fields are indicated with an asterisk (*).

(Must be between 3 – 31 characters.)

By clicking Submit, you agree to the developerWorks terms of use.

 


All information submitted is secure.

Dig deeper into Open source on developerWorks


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=1
Zone=Open source
ArticleID=219286
ArticleTitle=What's new in PHP V5.2, Part 5: Tracking file upload progress
publish-date=05152007