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/session_upload_progress

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: http://chemicaloliver.net/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']); ?>

PHP 5.4 File Upload Progress and HTML5 Progress Bars

  1. Phil Sturgeon says:

    Very useful! I’ve bookmarked this for future use as every single time I’ve done this in the past I’ve needed to use some crazy fuck combination of Flash, Perl and goat sacrifice.

    Thank you :)

    • chemicaloliver says:

      Thanks, it’s not perfect but of the only constraint is using native sessions it sounds a whole lot better than all the usual methods, using Flash solutions with Codeigniter has caused me lots of headaches.

      In my job I build apps for print order management which always involves uploading large files so it’s an issue close to my heart!

  2. Peter says:

    Thanks for a great and very useful tutorial. This particular problem has caused me lots of headaces too. The only okay solution i’ve found besides this with APC, but then there are some compability issues(does not work with Suhosin installed for example).
    I will bookmark this for use in coming projects.
    Thank you again!

    • chemicaloliver says:

      That is possible if you set up a websocket server to poll the PHP session variable and feed it down a websocket, but that’s a lot of trouble unless you already have the web socket server in place in your app.

      • Jonah Dahlquist says:

        Websockets is what I’m thinking. However, do the values update every time you check them? For example:


        session_start();
        while (true) {
        $status = $_SESSION['upload_progress_upload'];
        sleep(0.1);
        }

        Will it give updated information, or does it only update when the script starts?

        • chemicaloliver says:

          There are variables to set the rate in php.ini see the new RFC link I’ve added to the article.

  3. Rob Allport says:

    Definitely bookmarked for future projects. I’ve tended to use uploadify a lot in the past, which is fairly heavy weight.

    Didn’t even know there was “progress” tag either :)

    • Oliver says:

      I don’t currently have a PHP 5.4 server setup so I can’t test this. I developed the example using Firefox and it definitely worked fine. I don’t remember any issues with Chrome on Linux but I can’t be sure. I’ve added a warning above.

      • geocrunch says:

        Also, the easy way to make it work in Chrome and some other browsers is to change the form “target” to post into an “iframe”. Wheee!!! :)

  4. Igor says:

    You’re not working.

    I copied the code from github, my php is version 5.4 and the settings are ok, according to the PHP manual.

    The value returned is null session.

    Can anyone help me?

    Igor.

  5. ken says:

    This tutorial helped me a lot, thank you.

    While implementing this I had a problem that all my ajax calls (.getJSON) were stacking up and not completing until the original POST completed.

    The problem turned out to be PHP locking the session on the first process so none of the session reads in the progress.php script would complete until the session was freed.

    Solution was to use session_write_close() in the original process to close the session after each status message update. In my case I wanted to have a number of status messages throughout the process so I ended up wrapping each $_SESSION['status'] = ‘some new status’ with a session_start(); and session_write_close();

    After that it all worked well.
    thanks!
    ken

Leave a Reply