Programming and Technology
RSS icon Home icon
  • Upload progress meters with PHP

    Posted on July 28th, 2009 brandon No comments

    Upload progress meters have always been a point of pain when writing web applications in php. I don’t know if its still the case with php 5.3 but versions prior had no built in way to check on the status of a file upload mid-transit. This was especially irritating considering perl could do this. A progress meter is almost mandatory when building sites that handle very large file uploads, such as video hosting sites and the like. Fortunately there is a handy extension in the PECL repository called uploadprogress. This extends php a bit to include functions for monitoring uploads using a unique identifier that gets associated to each upload. I won’t go over how to install PECL extensions since that’s covered by plenty of documentation found on the web.

    Once installed and enabled in the php.ini configuration file we can start to do some nifty things. The first thing we need to do is include a hidden input with our file upload form that is populated with a unique number that will be used as a way of referencing a particular file upload. I usually generate a random number with the following method:

    1.  
    2. <?php
    3. $uid = time() . rand() % 100000;
    4. ?>
    5.  

    This is pretty much guarantees unique numbers since it combines the current timestamp with a large random number.  Take this number and insert the value into a hidden form input called UPLOAD_IDENTIFIER. This is a reserved name that php will be expecting and it will associated the value of that field to the file upload.

    1.  
    2. <input type="hidden" name="UPLOAD_IDENTIFIER" value="<?php echo $uid; ?>"/>
    3.  

    When the form is initially show the progress meter elements should all be hidden from the user as it doesn’t make sense to show them yet. I like to use moving gif images to show that something is happening, particularly, ones called ‘throbbers’. I found a great website here if you’re looking for a good source of ajax indicators and throbbers. Alternately, you can use a combination of javascript and css to build a progress bar that fills up as the upload progresses. For the sake of simplicity, I’m going to use a simple throbber and some spans that contain statistics for the upload… think of it as a power user’s progress meter.

    1.  
    2.   <div id="upload-throbber" class="upload-throbber" style="display: none">
    3.         <img src="/images/ajax-throbber.gif"/>
    4.          <div>Speed: <span class="upload-status-speed">0</span> Kb/s  Bytes: <span class="upload-status-bytes">0</span> / <span class="upload-status-total">0</span> Kb  Complete: <span class="upload-status-percent">0</span> %</div>
    5.   </div>
    6.  

    Initially the div containing the progress meter elements is set to display: none using the style attribute. We will toggle the visibility with some javascript that gets triggered when the form is submitted. The easiest way to do this is by hooking into the form’s onsubmit event like so:

    1.  
    2.   <form method="post" enctype="multipart/form-data" class="monitored_upload" action="upload.php" onsubmit="startUpload(<?php echo $uid; ?>, ‘monitored_upload’)">
    3.      <!– SNIP –>
    4.   </form>
    5.  

    Here we name a custom javascript function which will perform some logic before the form is submitted.  I have defined a couple of generic functions that are portable to just about any scenario as they are mostly decoupled from the specifics of the form we are designing. Jquery allows us to keep these functions small yet powerful:

    1.  
    2.   function startUpload(pUID, pContext){
    3.      $(‘form.’ + pContext).find(‘.ajax-upload-throbber’).show();
    4.      $(‘form.’ + pContext).find(‘.ajax-upload-field’).hide();
    5.      $(‘form.’ + pContext).submit();
    6.      updateUploadStatus(pUID, pContext);
    7.      return true;
    8.   }
    9.  

    This function takes two arguments, the unique identifer we generated for the upload, and a context. The context is nothing more than the class of the form we want to manipulate. The purpose of this parameter is so we can handle multiple forms on one page. With jquery’s find() function we can grab the element inside the form containing the throbber and toggle the visibility to show it. Now we call the second function which initiates the polling process that retrieves the actual status of the upload. Make sure to return true at the end of this function that way the submit event is allowed to propogate and form is actually submitted.

    1.  
    2.   function updateUploadStatus(pUID, pContext){
    3.    $.getJSON(‘/upload-status.php?uid=’ + pUID, function(res){
    4.       if (res.status){
    5.          if (res.percent < 100){
    6.             $(‘form.’ + pContext).find(’span.upload-status-speed’).html(Math.round(res.speed_average / 1024));
    7.             $(‘form.’ + pContext).find(’span.upload-status-bytes’).html(Math.round(res.bytes_uploaded / 1024));
    8.             $(‘form.’ + pContext).find(’span.upload-status-total’).html(Math.round(res.bytes_total / 1024));
    9.             $(‘form.’ + pContext).find(’span.upload-status-percent’).html(res.percent);
    10.             gUploadStatsTimer = setTimeout(‘updateUploadStatus(‘ + pUID + ‘, "’ + pContext + ‘")’, 1000);
    11.          } else {
    12.             $(‘form.’ + pContext).find(’span.upload-status-percent’).html(100);
    13.             clearTimeout(gUploadStatsTimer);
    14.          }
    15.       } else
    16.          gUploadStatsTimer = setTimeout(‘updateUploadStatus(‘ + pUID + ‘, "’ + pContext + ‘")’, 1000);
    17.    });
    18.   }
    19.  

    Again jquery eases our pain as we use the $.getJSON call to make an ajax call to the server and break down the response into the relevant statistics we want. This call expects to receive an object with the following attributes:  status: (1 for success, 0 for fail),  speed_average, bytes_uploaded, bytes_total, and percent. The values refer to the number of bytes so you will have to do some calculation to get the values into the desired unit of measurement. If the percent is less than 100 (meaning the upload is still in progress), the appropriate elements are updated with new values and then the function uses setTimeout to recursively call itself every second. The timer is stored in a global javascript variable which we use to cancel it when percent reaches 100.  Now the timing on all of this is by no means perfect so its possible status will be 0 because the upload has not yet been noticed on the php side of things so as you can see when status is 0, we use setTimeout any to call the function again. In ideal conditions, once the upload begins, the progress meter elements will be updated every second with the transfer rate, the byte count and the overall percentage. It makes for a pretty cool effect.

    So with all the client side stuff in place we just now need a backend script to respond to the ajax call that requests the status. The code to handle this request is stupid simple. Here’s upload-status.php

    1.  
    2. <?php
    3.   $stats = uploadprogress_get_info($_REQUEST[‘uid’]);
    4.  
    5.   header(‘Content-Type: application/x-json’);
    6.  
    7.   if ($stats){
    8.      $stats[’status’] = 1;
    9.      $stats[‘percent’] = round(($stats[‘bytes_uploaded’] / $stats[‘bytes_total’]) * 100);
    10.      echo Zend_Json::encode($stats);
    11.   } else {
    12.      $stats[’status’] = 0;
    13.      echo Zend_Json::encode($stats);
    14.   }
    15. ?>
    16.  

    Since I develop mostly using components from the Zend Framework, you can see that I’ve employed Zend_Json to handle encoding the response but you can use whatever method you prefer to create a JSON response. Likewise you can adapt your javascript however you like to handle the response if you would rather process xml or plain text. Either way, the call to uploadprogress_get_info (provided by the uploadprogress extension), will return an associative array when given  the unique identifier for an upload. A quick calculation is done in php so that we can include the percentage value as well. And you’re done.

    • Share/Bookmark
    Development, Javascript, PHP

    Related Topics

    • No Related Post

    Leave a reply

Get Adobe Flash playerPlugin by wpburn.com wordpress themes