File Upload Progress in PHP 5.4
With the release of PHP 5.4 the file upload progress feature becomes available. Yes, “available” as in “you can use it in your project now”.
This is a quick start guide with the least amount of code required, using jQuery’s compact AJAX requests and iframes.
The code
The code is subdivided into three main parts – the upload form, the AJAX requests and the upload progress response generation.
The form
The form is injected into the page via an iframe
, since Webkit browsers lock all JavaScript requests once an upload is underway.
<form action="" method="POST" enctype="multipart/form-data"> <input type="hidden" name="<?php echo ini_get("session.upload_progress.name"); ?>" value="demo"> <input type="file" name="uploaded_file"> <input type="submit" value="Upload"> </form> <script type="text/javascript"> window.location.hash = ""; /* reset */ jQuery("form").bind("submit", function() { window.location.hash = "uploading"; }); </script>
PHP listens for a POST variable matching the session.upload_progress.name
value. Other than that it’s a regular form. This code will be shown in the iframe
. Tracking of whether upload has started yet or not is done via the window.location.hash
value. There are many other valid (and probably better) methods, this is one I decided to try out today.
The AJAX requests
This is shown on the upload page itself, including the iframe
.
<iframe src="?iframe" id="upload_form"></iframe> <script type="text/javascript"> jQuery(document).ready(update_file_upload_progress); function update_file_upload_progress() { if ( window.frames.upload_form.location.hash != "#uploading" ) { setTimeout( update_file_upload_progress, 100 ); /* has upload started yet? */ return; } $.get( /* lather */ "?progress", function(data) { /* rinse */ jQuery("#file_upload_progress").html(data); /* repeat */ setTimeout( update_file_upload_progress, 500 ); /* call itself in # ms */ } ).error(function(jqXHR, error) { alert(error); }); } </script> <div id="file_upload_progress"></div>
The JavaScript is simple, constantly polls the hash of the upload iframe
and as soon as it changes starts polling the server for upload progress.
The response
if ( isset( $_GET['progress'] ) ) { $progress_key = strtolower(ini_get("session.upload_progress.prefix").'demo'); if ( !isset( $_SESSION[$progress_key] ) ) exit( "uploading..." ); $upload_progress = $_SESSION[$progress_key]; /* get percentage */ $progress = round( ($upload_progress['bytes_processed'] / $upload_progress['content_length']) * 100, 2 ); exit( "Upload progress: $progress%" ); }
The key for the session upload progress is assembled by combining the session.upload_progress.prefix
directive along with whatever value the form supplied for the progress upload hidden field.
The structure of the data inside the upload progress array is the following:
- int
start_time
- int
content_length
- int
bytes_processed
- bool
done
- array
files
- string
field_name
- string
name
(filename) - string
tmp_name
- int
error
- bool
done
- int
start_time
- int
bytes_processed
- string
All together on one page
<?php /* File upload progress in PHP 5.4 */ /* needs a 5.4+ version */ $version = explode( '.', phpversion() ); if ( ($version[0] * 10000 + $version[1] * 100 + $version[2]) < 50400 ) die( 'PHP 5.4.0 or higher is required' ); if ( !intval(ini_get('session.upload_progress.enabled')) ) die( 'session.upload_progress.enabled is not enabled' ); session_start(); if ( isset( $_GET['progress'] ) ) { $progress_key = strtolower(ini_get("session.upload_progress.prefix").'demo'); if ( !isset( $_SESSION[$progress_key] ) ) exit( "uploading..." ); $upload_progress = $_SESSION[$progress_key]; /* get percentage */ $progress = round( ($upload_progress['bytes_processed'] / $upload_progress['content_length']) * 100, 2 ); exit( "Upload progress: $progress%" ); } ?> <script type="text/javascript" src="https://code.jquery.com/jquery-1.7.1.min.js"></script> <?php if ( isset($_GET['iframe']) ): /* thank you Webkit... */ ?> <form action="" method="POST" enctype="multipart/form-data"> <input type="hidden" name="<?php echo ini_get("session.upload_progress.name"); ?>" value="demo"> <input type="file" name="uploaded_file"> <input type="submit" value="Upload"> </form> <script type="text/javascript"> window.location.hash = ""; /* reset */ jQuery("form").bind("submit", function() { window.location.hash = "uploading"; }); </script> <?php else: ?> <iframe src="?iframe" id="upload_form"></iframe> <script type="text/javascript"> jQuery(document).ready(init); function init() { /* start listening on submit */ update_file_upload_progress(); } function update_file_upload_progress() { if ( window.frames.upload_form.location.hash != "#uploading" ) { setTimeout( update_file_upload_progress, 100 ); /* has upload started yet? */ return; } $.get( /* lather */ "?progress", function(data) { /* rinse */ jQuery("#file_upload_progress").html(data); /* repeat */ setTimeout( update_file_upload_progress, 500 ); } ).error(function(jqXHR, error) { alert(error); }); } </script> <div id="file_upload_progress"></div> <?php endif; ?>
Troubleshooting
If PHP’s new file upload progress feature does not work ($_SESSION
is empty, for example) for you check and make sure of the following:
- File is large enough for at least 5 seconds of uploading
- Server upload and request size limits are OK
- session.upload-progress.enabled is set to “1”
- session.upload-progress.freq and session.upload-progress.min-freq are also adequate for the file sizes you’re working with
- file_uploads is set to “1”
- upload_max_filesize is set to an adequate number, you’ll probably be experimenting locally with large fast sizes
- upload_tmp_dir is writable
- post_max_size is as wide enough to accommodate your requests
- Check the error messages in the
$_FILES
array to get additional information
Other methods
The new feature is very convenient in many cases, however it has its disadvantages, like the constant polling of the server, the iframe
, among others. Upload progress indicators with a lot of bells and whistles come in such upload plugins as Plupload (used in WordPress 3.3), SWFupload, etc. and should be used in larger-scale projects.
Conclusion
PHP’s new file upload progress function is lightweight, useful and interesting to work with. It will never match more powerful solutions with their multiple-file selections, built-in image converters and client-side progress calculation, but it’s something that is easy to setup, is quite manageable and clean.
So, have you tried it already? Are you using it in any of your projects? Hit any limitations? Had any difficulties in setting it up?
I’m interest in build a runtime for plupload (as HTML 4), but adding upload progress using PHP5.4
Check out the plupload forums, I think someone may already have suggested this, but there’s really no need to, upload progress solutions have always been available, PHP 5.4 just brought one closer.
Did you actually get this to work? I only ever have an empty session and it crashes on files smaller than the max_post_size…? Is it just me?
Drew, yes, worked quite well for me, have you checked out the troubleshooting section above?
Nope. Never got it to work. Note that I am using the command line PHP server…perhaps that has limitations…?
Can’t find anything saying that the new built-in development server (
php -S address:port
) has any limitations. Are the files uploaded? Is the session global available? Have you tried using another webserver?this code:
https://github.com/chemicaloliver/PHP-5.4-Upload-Progress-Example
is cleaner code for this tutorial.
i get empty session in local test too.
i change the php.ini setting to these for test upload progress:
session.upload_progress.enabled = 1
session.upload_progress.cleanup = 0
session.upload_progress.freq = “1%”
session.upload_progress.min_freq = “1”
post_max_size = 2000M
file_uploads = 1
upload_max_filesize = 2000M
and it works fine.
Many thanks for your comments and your input! 🙂
firebug says: TypeError: window.frames.upload_form is undefined
Perhaps you misnamed your frame? If you find out what’s wrong, do let me know; thanks.
just add: name=”upload_form” to the iframe. btw i, and some other trapped victims have empty sessions. It is driving me crazy. php should work on that seriously. 3/2014 and stil mid-age technology. thanks for the tutorial anyway.
Works for me. I tweaked a few things when implementing into my code, just a few personal preferences really. Good tutorial. Thanks.
Not far from what Temirbek reported, I got a Firebug error message “window.frames.upload_form.location is undefined”.
Then it worked fine with “window.frames[0].location” instead!
In any case, thanks for this article, where I learned many things.
What should i name the files please?
Not sure what you mean, Patrick. Can you provide more information on the issue you’re having?
Please help me !
I got a Console error message :
Uncaught TypeError: Cannot read property ‘hash’ of undefined !
Weird, open up your browser’s developer console and try looking at `window.location` or just `location` it should have the `hash` attribute. What browser are you using? See https://developer.mozilla.org/en-US/docs/Web/API/Window.location for more information
I just retrieve the code for “All together on one page” and put it in a php file and when I click in the iframe to upload a file after you have selected the function window.location.hash = “uploading” does not execute the click.
I use google chrome but firefox I just s’ appears during the upload.
I testing on https://attestament.com/uploading.php
This is the ajax request that does not have works!
The side of the PHP I’m progression that appears during an upload if you put the parameter get “progress” in the url!
This is good! I found the solution, it lacked the attribute “name” in the code of the iframe !
Thinks
Many thanks for this tutorial that even discusses pros and cons.
Originally I had implemented a file upload without progress bar but with some additional functions like email notification. It runs quite well for all sizes of files my customer needs, even for large files up to 10 GB.
Unfortunately, the progress bar only shows up for small files. Trying a file of 6 GB results in an empty session. Maybe, somebody knows the reason and how to cope with it.
Update: Using the function is_int revealed that large file sizes aren’t integers any longer.
That makes sense, thanks for letting me know.