Symfony 1.1 introduced the ability to embed forms inside other forms. Symfony 1.2 enhanced this feature greatly by providing the ability to automatically save any related objects found within these nested forms (see this blog post). Another new feature introduced in 1.2. is the ability to automatically handle file uploads in forms. Basically if you have a propel object with a file field, and define this field in your form validation schema as an sfValidatorFile, symfony takes care of removing any old field, saving the new one, and updating the column in the object to reflect the new file name. Pretty sweet. (You can read more about this feature in What’s New in 1.2.)
Everything is nearly perfect, except one problem: File fields in embedded forms are *not* processed automatically. It took me a long time to track down the issue, but it was a good opportunity for me to explore some of the new sfForm framework and really get under the hood to understand how it all works.
When you call save() on a form, here is a summary of the methods called:
$form->save() | $form->doSave() // updates the object, and then saves the object | $form->updateObject() | $form->processValues() // sets $values | $form->processUploadedFile($field); | $form->getValue($field) | $form->saveFile($field) | $form->object->fromArray($values, BasePeer::TYPE_FIELDNAME); | $form->object->save()
When processValues() encounters a field that has a validator of the class sfValidatorFile, it calls processUploadedFile(). Let’s take a look at this method:
/** * Saves the uploaded file for the given field. * * @param string $field The field name * @param string $filename The file name of the file to save * * @return string The filename used to save the file */ protected function processUploadedFile($field, $filename = null) { if (!$this->validatorSchema[$field] instanceof sfValidatorFile) { throw new LogicException(sprintf('You cannot save the current file for field "%s" as the field is not a file.', $field)); } if ($this->getValue($field.'_delete')) { $this->removeFile($field); return ''; } if (!$this->getValue($field)) { $column = call_user_func(array(constant(get_class($this->object).'::PEER'), 'translateFieldName'), $field, BasePeer::TYPE_FIELDNAME, BasePeer::TYPE_PHPNAME); $getter = 'get'.$column; return $this->object->$getter(); } // we need the base directory if (!$this->validatorSchema[$field]->getOption('path')) { return $this->getValue($field); } $this->removeFile($field); return $this->saveFile($field, $filename); }
The problem is, when an embedded form is processed, it never gets bound to its [cleaned] values present in the parent form. So, any calls to getValue() from the embedded form comes back empty. That renders the processUploadedFile() useless when called from an embedded form.
The best fix I could find for this is to override the parent form’s bind() method so that it would in turn assign cleaned values and set the isBound status of its nested forms.
public function bind(array $taintedValues = null, array $taintedFiles = null) { $ret = parent::bind($taintedValues, $taintedFiles); foreach ($this->embeddedForms as $name => $form) { $this->embeddedForms[$name]->isBound = true; $this->embeddedForms[$name]->values = $this->values[$name]; } return $ret; }
Once this new method is in place in the parent form’s class, the file was being processed as intended. I’m not sure if this was just an oversight or is a bug or what, but I’m sure other symfony developers will encounter this issue just as I have. If you have a better solution, please share it with us!!




But this solution don’t work if main form have file field
Any things about this?
By eugeneis on Feb 11, 2009
@eugeneis, if your mail form has a file field just use the normal configuration as outlined in the docs, http://www.symfony-project.org/tutorial/1_2/whats-new#Forms and scroll down to where it says “sfFileValidator”
By Scott Meves on Feb 11, 2009
Parent form is main form in which I embed form with upload input?
How to get value of a file object? I embeded form like this:
$this->embedForm(‘skan’, $skan_form);
Main form has name: dokument
Like this?
$file = $this->form->getValue(‘dokument[skan][file_name');
By Tom on Feb 12, 2009
just awesome =) thanks so much !
By country trek on Feb 16, 2009
It works perfectly, good job !
By BIOT on Feb 25, 2009
Yeahhh, After a loss a lot of time finding a solution, i found this post, and it work fine. I’m using symfony 1.1 my bind was well:
public function bind(array $taintedValues = null, array $taintedFiles = null)
{
$ret = parent::bind($taintedValues, $taintedFiles);
foreach ($this['rooms'] as $key => $form) {
$this['rooms'][$key]->isBound = true;
$this['rooms'][$key]->values = $form->getValue();
}
return $ret;
}
Thanks for this post.
By Nei Rauni Santos on Feb 26, 2009
Scott thank you very much, I was getting crazy with this issue!
But I can’t figure out how to customize the upload process for embedded forms :’(
By uccio on Feb 26, 2009
Hi, I created a custom validator to validate a group of embedded forms, it’s postValidator, when I POST the form without error i can access all values.
But when i give a error on form, my postValidator method can’t access the embedded values.
Any idea about it?
Thanks,
Nei
By Nei on Mar 5, 2009
hey awesome! works great for me! thanks for the good explanation too.
By rekarnar on Apr 22, 2009
Hi
This one works for me also but i dont want random filename, i would like to have filename coresponding with picture ID in my database.
previously I used this method to get such result:
class Picture extends BasePicture
{
public function generateUrlFilename(sfValidatedFile $file)
{
return $this->getId().$file->getExtension($file->getOriginalExtension());
}
}
It dont work with this tutorial couse while file is saved to disk its not yet saved in database.
Anyone can help me fix my problem?
By Lukis on Apr 28, 2009
I had the same problem, and found that there may be a bug in the function processUploadedFile().
At the end, it calls : return $this->saveFile($field, $filename);
If you’re looking in saveFile() params, you see there are 3 : $field, $filename and $file. If $file is null, the method take it from $this->values :
if (is_null($file))
{
$file = $this->getValue($field);
}
As you say, the problem came here, as the form object still does not have values.
But wait, the method processUploadedFile() which calls saveFile() HAS the value itself.
So, if you replace :
return $this->saveFile($field, $filename);
bye :
return $this->saveFile($field, $filename, $values[$field]);
Tadaaaam ! It works !
By nerVo on Apr 30, 2009
Great!
thanks a lot, I couldn’t fix this problem
By leonardo lazzaro on Jun 13, 2009
When embedding a file form into another form, I got “Unexpected extra form field named…” with the name of my embedded form. Any ideas?
By Raul Ramos on Oct 14, 2009
Thank you thank you thank you for the new $form->bind() override!! My form was functioning properly when submitting by itself when not embedded, but when it was embedded in another form, I could not access $this->getValue(‘myfield’) in the embedded form class. Your method of iterating through each embedded form and manually-binding the submitted values fixed everything!
By John Kary on Nov 1, 2009
awsome great work
this will be handy and I know someone that can use it aswell. Thank you
By dwitv on Nov 13, 2009
thank you for sharing this embed code.
and great usefull help in the comments
By rv park on Nov 13, 2009
great tips thanks alot
By Vancouver Marinas on Nov 17, 2009
Hi, I’ve tryed this and it works fine, when the file field has value. But when I’m editing back the record and don’t change the file. The file gets deleted. Anyone of you had the same problem?
Tia
By Pedro on Dec 6, 2009
great i know someone that need this now i can send them this link
By Mafia Planet on Jul 28, 2010
Hello,
Does anybody know whether it is still necessary to modifiy bind() with Symfony 1.4.9?
In Symfony documentation they don’t say much about the saving process…
Cheers
By Laurent on Apr 10, 2011
Laurent you are better off looking on a forum for some help i would think.
By Road Trip on Jul 5, 2011