PHP 5.4 File Upload Progress and HTML5 Progress Bars

This posts was originally written for using pre release versions of php5.4, now PHP5.4 has been release everything should work as before I haven't tested it. There is probably more choice in ways to install it now.

There was a live demo of this but I couldn't justify the cost of a whole ec2 instance just for this!

The way I have implemented this may be problematic in Chrome/Safari due to Webkit Bug 23933,  "XMLHttpRequest doesn't work while submitting a form (useful for progress tracking)" I tested this in firefox (the latest version at the time) and it worked fine. Chrome may or may not work. Let me know how you get on.

Full details of the upload progress implementation can be found the official RFC: https://wiki.php.net/rfc/sessionuploadprogress

Developers who work with PHP applications that upload files commonly struggle with providing user feedback on the upload progress, usually using flash and javascript solutions like uploadify. In PHP 5.4 there is now integrated functionality to allow file upload progress to be passed back to the browser.

In this post I'll describe the basic operation of this feature and describe a quick example of its use.

How it Works

The upload progress functionality stores the current progress in a session variable which can then be queried as required to give the current progress, it requires the use of PHP native sessions. The $_SESSION key is set by the form name and a prefix defined in php.ini

An example of the data stored is shown below:

<?php 
$_SESSION['upload_progress_123'] = array(  'start_time' = 1234567890,  'content_length' = 57343257,
 'bytes_processed' > 453489,
 'done' > false,
 'files' > array(
  0 > array(
   'field_name' > 'file1',
   // The following 3 elements equals those in $_FILES
   'name' > 'foo.avi',
   'tmp_name' > '/tmp/phpxxxxxx',
   'error' > 0,
   'done' > true,
   'start_time' > 1234567890,
   'bytes_processed' > 57343250,
  ),
  // An other file, not finished uploading, in the same request
  1 > array(
   'field_name' > 'file2',
   'name' > 'bar.avi',
   'tmp_name' > NULL,
   'error' > 0,
   'done' > false,
   'start_time' > 1234567899,
   'bytes_processed' > 54554,
  ),
 )
);
?>

It is up to the developer how they wish to present this data to the user.

Setup

This functionality is included as standard in PHP 5.4 which can be simply installed from an ubuntu package as discussed in my previous post: /internet/installing-php-5-4-in-ubuntu/

This feature should be enabled by default.

Example

Complete code can be found on github.

Upload Form

The upload form is standard html file upload form apart from an additional hidden value defining the progress name attribute so the upload can be identified in the session variables later:

<form id='upload' action='/progress/upload.php' method='POST' enctype='multipart/form-data'><input type='hidden' name='' value='upload' />

 <input id='file1' type='file' name='file1' />
 <input id='file2' type='file' name='file2' />

 <input class='btn primary' type='submit' value='Upload' /></form>

In my example I've used the jquery form plugin to make submitting the form using AJAX more simple.

Recieving Script

In this example the receiving script needs to do nothing except start a session, I added a var_dump for debugging initially:

<?php session_start(); var_dump($_SESSION); var_dump($_FILES);?>

Monitoring Progress

This is performed through a combination of javascript and PHP, the client polls a server page which echos the current progress as JSON:

Client:

The client uses an interval time to get the progress every 200ms and pass it to an HTML5 progress bar.

interval_id = setInterval(function() {
$.getJSON('/progress/progress.php', function(data){
    //if there is some progress then update
    if(data)
    {
        $('#progress').val(data.bytes_processed / data.content_length);
        $('#progress-txt').html('Uploading '+ Math.round((data.bytes_processed / data.content_length)*100) + '%');
    }

    //When there is no data the upload is complete
    else
    {
        $('#progress').val('1');
        $('#progress-txt').html('Complete');
        stopProgress();
    }
})
}, 200);

Server Side:

The server side just echos a json encoded version of the session variable:

<?php session_start(); echo json_encode( $_SESSION['upload_progress_upload']); ?>