Simple Zend_Form File Upload Example
Zend Framework 1.5's Zend_Form component is missing support for the file input element as it is waiting on a file upload component to build upon. We're busy people, so we'll fake it…
This is a super simple example showing how to do file uploads with Zend_Form in Zend Framework 1.5.
I'm building on the Simple Zend_Form Example, so make sure you have read that and that it works before you start this one.
This is what the form looks like:

(Unstyled, as usual!)
This is what we need to do:
The form
The form is an extension of Zend_Form and is stored in app/forms/UploadForm.php and so the class name is forms_UploadForm:
<?php class forms_UploadForm extends Zend_Form { public function __construct($options = null) { $this->addElementPrefixPath('App', 'App/'); parent::__construct($options); $this->setName('upload'); $this->setAttrib('enctype', 'multipart/form-data'); $description = new Zend_Form_Element_Text('description'); $description->setLabel('Description') ->setRequired(true) ->addValidator('NotEmpty'); $file = new App_Form_Element_File('file'); $file->setLabel('File') ->setRequired(true) ->addValidator('NotEmpty'); $submit = new Zend_Form_Element_Submit('submit'); $submit->setLabel('Upload'); $this->addElements(array($description, $file, $submit)); } }
We are going to create our own validator class within our own library namespace (lib/App) and so we need to tell the form about this using addElementPath(). We then set the name and enctype attribute of the form to allow for files to be uploaded.
The form has two fields: a text field called 'description' and the file upload field called 'file', along with a submit button. As Zend Framework doesn't have it's own file element, we will create our own called App_Form_Element_File.
The file form element
The file element, App_Form_Element_File, is stored in lib/App/Form/Element/File.php and looks like this:
<?php require_once 'Zend/Form/Element/Xhtml.php'; class App_Form_Element_File extends Zend_Form_Element_Xhtml { /** * Flag indicating whether or not to insert ValidFile validator when element is required * @var bool */ protected $_autoInsertValidFileValidator = true; /** * Default view helper to use * @var string */ public $helper = 'formFile'; /** * Set flag indicating whether a ValidFile validator should be inserted when element is required * * @param bool $flag * @return Zend_Form_Element */ public function setAutoInsertValidFileValidator($flag) { $this->_autoInsertValidFileValidator = (bool) $flag; return $this; } /** * Get flag indicating whether a ValidFile validator should be inserted when element is required * * @return bool */ public function autoInsertValidFileValidator() { return $this->_autoInsertValidFileValidator; } public function isValid($value, $context = null) { // for a file upload, the value is not in the POST array, it's in $_FILES $key = $this->getName(); if(null === $value) { if(isset($_FILES[$key])) { $value = $_FILES[$key]; } } // auto insert ValidFile validator if ($this->isRequired() && $this->autoInsertValidFileValidator() && !$this->getValidator('ValidFile')) { $validators = $this->getValidators(); $validFile = array('validator' => 'ValidFile', 'breakChainOnFailure' => true); array_unshift($validators, $validFile); $this->setValidators($validators); // do not use the automatic NotEmpty Validator as ValidFile replaces it $this->setAutoInsertNotEmptyValidator(false); } return parent::isValid($value, $context); } }
Zend Framework already provides a view helper for displaying a file element, formFile, so we set the $helper member variable to 'formFile' so that the correct element is rendered when the form is displayed. We then need to ensure that validation is handled correctly. For all other form elements the value of the field is returned in the POST array. For a file, this is not true as the data is with the $_FILES global array. We could handle this in the controller, but by putting it in the element class, we never have to think about it again. The isValid() member function is used to set the value for an element and also run the validator chain to determine if the value is valid or not.
We override isValid() for the file element to provide two functionalities:
* Set the value to the contents of correct sub-array of the $_FILES array .
* Automatically turn on a custom validator called ValidFile which will check if the upload succeeded.
The file element also has some helper functions (setAutoInsertValidFileValidator and getAutoInsertValidFileValidator) to control the auto-insertion of the ValidFile validator. Note that if we do automatically insert the ValidFile validator, then we turn off Zend_Form_Element's automatic NotEmpty validator as it is redundant.
Once we have set the $value variable correctly and inserted our validator, we call up to the parent's isValid() function which will run the validation chain for us.
The ValidFile validator
The ValidFile validator's full class name is App_Validate_ValidFile and so it's filename is lib/App/Validate/ValidFile.php. This class handles validating the 'error' field within the $value array (which as you'll recall came from $_FILES). This is the code:
<?php class App_Validate_ValidFile extends Zend_Validate_Abstract { const INI_SIZE = 'iniSize'; // Value: 1; The uploaded file exceeds the upload_max_filesize directive in php.ini const FORM_SIZE = 'formSize'; // Value: 2; The uploaded file exceeds the MAX_FILE_SIZE directive that was specified in the HTML form. const PARTIAL = 'partial'; // Value: 3; The uploaded file was only partially uploaded. const NO_FILE = 'noFile'; // Value: 4; No file was uploaded. const NO_TMP_DIR = 'noTmpDir'; // Value: 6; Missing a temporary folder. const CANT_WRITE = 'cantWrite'; // Value: 7; Failed to write file to disk. const EXTENSION = 'extension'; // Value: 8; File upload stopped by extension. Introduced in PHP 5.2.0. const ERROR = 'error'; // General error for future proofing against new PHP versions /** * @var array */ protected $_messageTemplates = array( self::INI_SIZE => "The uploaded file exceeds the upload_max_filesize directive in php.ini", self::FORM_SIZE => "The uploaded file exceeds the MAX_FILE_SIZE directive that was specified in the HTML form", self::PARTIAL => "The uploaded file was only partially uploaded", self::NO_FILE => "No file was uploaded", self::NO_TMP_DIR => "Missing a temporary folder", self::CANT_WRITE => "Failed to write file to disk", self::EXTENSION => "File upload stopped by extension", self::ERROR => "Unknown upload error" ); /** * Defined by Zend_Validate_Interface * * Returns true if and only if $value[error] is equal to UPLOAD_ERR_OK. * * @note: This validator expects $value to be the array from $_FILES * * @param array $value * @return boolean */ public function isValid($value) { // default value and error is "no file uploaded" $valueString = "; $error = UPLOAD_ERR_NO_FILE; if(is_array($value) && array_key_exists('error', $value)) { // set the error to the correct value $error = $value['error']; // set the %value% placeholder to the uplaoded filename $valueString = $value['name']; } $this->_setValue($valueString); $result = false; switch($error) { case UPLOAD_ERR_OK: $result = true; break; case UPLOAD_ERR_INI_SIZE: $this->_error(self::INI_SIZE); break; case UPLOAD_ERR_FORM_SIZE: $this->_error(self::FORM_SIZE); break; case UPLOAD_ERR_PARTIAL: $this->_error(self::PARTIAL); break; case UPLOAD_ERR_NO_FILE: $this->_error(self::NO_FILE); break; case UPLOAD_ERR_NO_TMP_DIR: $this->_error(self::NO_TMP_DIR); break; case UPLOAD_ERR_CANT_WRITE: $this->_error(self::CANT_WRITE); break; case 8: // UPLOAD_ERR_EXTENSION isn't defined in PHP 5.1.4, so use the value $this->_error(self::EXTENSION); break; default: $this->_error(self::ERROR); break; } return $result; } }
Although this class is quite long, not much happens! All the work happens in isValid() where we set $error to the 'error' value originally from the $_FILES array and then set our error message based on which error PHP has reported to us. If PHP reported no error (UPLOAD_ERR_OK), then we return true, otherwise we return false after having set the error value.
The controller
Lastly, the controller is essentially identical to what we did in the Simple Zend_Form Example:
<?php class IndexController extends Zend_Controller_Action { function indexAction() { $this->view->pageTitle = "Zend_Form File Upload Example"; $this->view->bodyCopy = "<p>Please fill out this form.</p>"; $form = new forms_UploadForm(); if ($this->_request->isPost()) { $formData = $this->_request->getPost(); if ($form->isValid($formData)) { // success - do something with the uploaded file $uploadedData = $form->getValues(); Zend_Debug::dump($uploadedData, '$uploadedData'); exit; } else { $form->populate($formData); } } $this->view->form = $form; } }
If it all validates correctly, then you need to process the uploaded data. For this example we just display it using Zend_Debug::dump(), so you can see what it looks like:

Conclusion
As you can see, although Zend Framework 1.5 doesn't have file upload element built in, adding one is not especially hard. In fact, we have more code used to validate that and provide an error message on failure than we do for the upload itself!
As usual, here's a zip file of this project: Zend_Form_FileUpload_Example.zip (It includes Zend Framework 1.5.1 which is why it's 3.9MB big).
Test it out and maybe use it as the basis of your file uploading needs with Zend_Form.

April 7th, 2008 at 07:53 #
Great post, I've been looking at this for a project one of our trainees is doing, we ended up with a simple Zend_Form_Element with the helper set, will go on to implement everything you've got here now.
April 7th, 2008 at 11:34 #
Very nice tutorial, Rob! Keep 'em coming!
April 7th, 2008 at 11:41 #
Great tutorial, thanks!
I've found some missing things in
App_Validate_ValidFile::isValid()
There's one double-quote only:
$valueString = ";
Anyway, thank you for very helping post.
April 7th, 2008 at 13:02 #
Dominik,
That's two single quotes in the database. I wonder which plugin is "auto-converting" it…
Regards,
Rob…
April 7th, 2008 at 18:53 #
I love your work Rob and I use ZF myself but when it takes over 200 lines of code to do a file upload something is not right. Now, I'm not trying to criticize you in any way (heck I will be buying your book when it comes out) and I think you have presented a very solid solution within the Zend Framework but damn that is a lot of code just to upload and validate a file.
I went and checked with Code Igniter to see what it took to accomplish a similar upload/validation and found a solution that takes less than 1/3 as much code to accomplish. No it doesn't provide as fine grained control over errors as your example does but overall, as a web developer, that kind of strikes me as a pretty substantial difference in productivity. Don't you think?
April 7th, 2008 at 19:40 #
Hi RST,
Part of the issue is that there isn't a ZF solution at all at the moment. When there is, then the code you'd write will be just:
If minimising code code had been a goal, I could have reduced the App_Validate_ValidFile class down to probably 6 to 10 lines, but I chose to go to the last mile and cover everything, include future proofing for new error codes. Whilst I may not need that granularity on most projects, on the ones that I do, it's nice not to have to write any new code.
Final thought: For code that is completely reusable like this, I don't particularly mind the size. I've written this once and have reused it on 3 projects so far, so the cost of writing trends to trivial.
Regards,
Rob…
April 9th, 2008 at 20:45 #
Rob,
Thanks for the example. We are currently struggling with providing a progress bar during the upload. Our customers upload large (hundreds of megabytes) files to our site and they really want a progress bar to know it is working. Does the above do anything with that or is it the "quiet until done or error" type of upload.
We haven't found a Php solution that works and are looking at a Perl one but that one is problematic. Any guidance or hope you can give us?
Thanks!
John
April 9th, 2008 at 22:57 #
@John
Try having a look at APC (http://uk3.php.net/apc), the newer versions have hooks for monitoring file uploads, only problem being they're not thread safe.
April 10th, 2008 at 07:08 #
JAB,
Doesn't YUI have a progress bar thing? Might be worth a look.
Regards,
Rob…
April 10th, 2008 at 12:46 #
Rob,
Thank you for this tutorial. It's a great one.
Have you scheduled a bit of Ajax in the tutorials to come? It is still quite unclear to me how to submit a form and process it the Ajax way with the ZF, and I'm afraid the doc doesn't cut it for the average php guy that I am.
:)
April 10th, 2008 at 20:00 #
When I uploaded a file, I received this Warning:
Warning: htmlspecialchars() expects parameter 1 to be string, array given in /var/www/site/library/Zend/View/Abstract.php on line 786
April 11th, 2008 at 01:13 #
Thanks! It's great.
April 11th, 2008 at 09:19 #
Why not propose this for inclusion in the Framework? There's a couple of proposals that mention it, but they don't fit with Zend_Form like this does.
April 11th, 2008 at 09:23 #
Dave,
When Zend_FileUpload or whatever is completed, I'm sure Matthew ensure that a kick-ass solution is implemented :)
Regards,
Rob…
April 11th, 2008 at 16:00 #
I get the same warning as Johnathan.
Warning: htmlspecialchars() expects parameter 1 to be string, array given in C:\home\zendtest\library\Zend\View\Abstract.php on line 786
It's an issue with the validation: if I fill out both fields properly the form submits; otherwise the warning.
Sam
April 13th, 2008 at 09:39 #
I discovered the same htmlspecialchars error while developing my own file upload in Zend. I modified you isValid method a little bit:
public function isValid($value, $context = null)
{
// for a file upload, the value is not in the POST array, it's in $_FILES
$key = $this->getName();
$fileValue = $value;
if(null === $fileValue) {
if(isset($_FILES[$key])) {
if($_FILES[$key]['error'] != UPLOAD_ERR_NO_FILE ||
!$this->getAllowEmpty() ||
$this->isRequired()) {
$fileValue = $_FILES[$key];
}
}
}
$return = parent::isValid($fileValue, $context);
// parent called setValue so let us overwrite it
$this->setValue($value);
}
You also need test for the required and allowEmpty values.
April 16th, 2008 at 04:22 #
In response to the comments about the htmlspecialchars warning, the reason for that error is that the isValid() function is converting the string $value to an array from $FILES. The controller is expecting this array (during success) while the view is expecting a string to make a textbox (during error). The easiest way to fix it is to just revert to the original value whenever an error occurs.
$key = $this->getName();
$fileValue = $value;
if(null === $value) {
if(isset($_FILES[$key])) {
$fileValue = $_FILES[$key];
}
}
… no change …
$return = parent::isValid($fileValue, $context);
if(!$return){
//its not valid so revert to the original value
$fileValue = $value;
}
// parent called setValue so let us overwrite it
$this->setValue($fileValue);
Hopefully, this will be helpful to someone else.
April 16th, 2008 at 17:01 #
Thanks for the code and explanation ed, you were right. I added
return $return;
to the end to make it work.
One issue: when you leave the description field blank and upload a valid form, the warning appears. I guess at that point the value is the FILES array and as such will trigger the warning.
Sam
April 18th, 2008 at 15:56 #
So, is it correct?
public function isValid($value, $context = null)
{
// for a file upload, the value is not in the POST array, it's in $_FILES
$key = $this->getName();
$fileValue = $value;
if(null === $value) {
if(isset($_FILES[$key])) {
$fileValue = $_FILES[$key];
}
}
// auto insert ValidFile validator
if ($this->isRequired()
&& $this->autoInsertValidFileValidator()
&& !$this->getValidator('ValidFile'))
{
$validators = $this->getValidators();
$validFile = array('validator' => 'ValidFile', 'breakChainOnFailure' => true);
array_unshift($validators, $validFile);
$this->setValidators($validators);
// do not use the automatic NotEmpty Validator as ValidFile replaces it
$this->setAutoInsertNotEmptyValidator(false);
}
//return parent::isValid($value, $context);
$return = parent::isValid($fileValue, $context);
if(!$return){
//its not valid so revert to the original value
$fileValue = $value;
}
// parent called setValue so let us overwrite it
$this->setValue($fileValue);
return $return;
}
April 28th, 2008 at 05:28 #
Just a simple spelling error (well, two of them):
self::ERROR => "Uknown uplaod error"
Other than that, great job! I'm glad I didn't try to do file upload stuff in ZF a couple weeks ago :)
April 28th, 2008 at 08:17 #
Thanks Cully,
I have updated.
Regards,
Rob..
April 29th, 2008 at 05:59 #
Hi…
How to fix Warning: htmlspecialchars() with this use…
I've 3 File form element in my form…
When form is submit and validation don't pass form is populate with date that have been submit. But I think in the same case… ZF is trying to poupulate file element with the array… How can i fix it ???
File Element should be populate with File name…
Can you help me….
Thanks…
April 29th, 2008 at 15:59 #
I want to make a form with SubForms with FileUpload, other SubForms and Text between the forms.
But how can I get Text between the forms? Till now I create a Hidden field with a legend (filled with the text), but now there's the problem, that I can't insert links or any HTML.
Also I want to add a HTML-Table and other Things between the forms and I don't know how to do this. Is there no HTML-Element in the Zend_Form?
And is it really impossible to add HTML-Code into a legend?
April 30th, 2008 at 10:14 #
hi ,
I have implemented this upload example … but I got these errors ..
Warning: Zend_Loader::include_once(forms/UploadForm.php) [function.Zend-Loader-include-once]: failed to open stream: No such file or directory in /opt/lampp/htdocs/gen/library/Zend/Loader.php on line 83
Warning: Zend_Loader::include_once() [function.include]: Failed opening 'forms/UploadForm.php' for inclusion (include_path='.:./library:../application/models/:../application/forms/:.:/opt/lampp/lib/php') in /opt/lampp/htdocs/gen/library/Zend/Loader.php on line 83
Fatal error: Class 'forms_UploadForm' not found in /opt/lampp/htdocs/gen/application/controllers/IndexController.php on line 42
please help me find where I have gone wrong …
-
Trinath
May 1st, 2008 at 15:31 #
I changed parent::isValid($value, $context);
to
if(is_array($value)){
// its not valid so revert to the original value
// parent called setValue so let us overwrite it
$this->setValue($value['name']);
} else {
parent::isValid($value, $context);
}
No more errors and everything works just fine.
May 1st, 2008 at 15:56 #
Hi Kudose,
That's a nice simple solution :)
Regards,
Rob….
May 9th, 2008 at 04:25 #
Hi, i have a problem with the example. No file is uploaded to the tmp folder and i get no error message. Here is the result:
$uploadedData array(3) {
["description"] => string(10) "NetAbalone"
["file"] => array(5) {
["name"] => string(24) "fred-droits-d'auteur.txt"
["type"] => string(10) "text/plain"
["tmp_name"] => string(42) "C:\Program Files\Zend\Core\temp\php692.tmp"
["error"] => int(0)
["size"] => int(2913)
}
["submit"] => string(6) "Upload"
}
I was unable to find the problem.
Greetings, Nicolas