SUBSCRIBE VIA RSS


Subscribe to our feed

Symfony Experts

Symfony Experts
If you have an urgent question for a symfony-related issue, this is the place to ask.

Topics

Stack Overflow


The old fashioned way

RECENT TUNES

December 23, 2008 โ€“ 9:09pm Symfony 1.2: upload a file inside an embedded form

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!!

Posted by in  Uncategorized   |  

28 Responses to Symfony 1.2: upload a file inside an embedded form

  1. Pingback: Symfony Form: ?????????? ????????????? ???? ??? ?????? ? ?? | ?????? ??? ????????????

  2. Pingback: Stereo Interactive & Design » Uploading a file with Symfony 1.2

  3. eugeneis says:

    But this solution don’t work if main form have file field ๐Ÿ™
    Any things about this?

  4. Scott Meves says:

    @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”

  5. Tom says:

    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’);

  6. country trek says:

    just awesome =) thanks so much !

  7. BIOT says:

    It works perfectly, good job !

  8. 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.

  9. uccio says:

    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 :'(

  10. Nei says:

    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

  11. rekarnar says:

    hey awesome! works great for me! thanks for the good explanation too.

  12. Lukis says:

    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?

  13. nerVo says:

    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 !

  14. Great!
    thanks a lot, I couldn’t fix this problem

  15. Raul Ramos says:

    When embedding a file form into another form, I got “Unexpected extra form field named…” with the name of my embedded form. Any ideas?

  16. John Kary says:

    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!

  17. dwitv says:

    awsome great work ๐Ÿ˜€
    this will be handy and I know someone that can use it aswell. Thank you

  18. rv park says:

    thank you for sharing this embed code.

    and great usefull help in the comments ๐Ÿ˜€

  19. Pedro says:

    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

  20. Pingback: Robert's Blog » Blog Archive » Internationalized (i18n) Admin Generator CRUD’s in Symfony 1.2.9 + Doctrine

  21. Mafia Planet says:

    great i know someone that need this now i can send them this link

  22. Laurent says:

    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

  23. Road Trip says:

    Laurent you are better off looking on a forum for some help i would think.

  24. Roman says:

    Hi!

    Did you mean to put bind() method in sfFormDoctrine class, right?

    Regards,
    Roman

  25. philwagn says:

    I lost also a lot of time on this problem…
    Your solution works very good !
    Now I can access to all the cleaned values (In my case the original filename after an upload)

    ๐Ÿ˜‰

  26. Pingback: image upload in nested forms | Find Answer

  27. Pingback: embedding sfForm in a sfPropelForm, sfForm validates but then doesn’t save properly [SOLVED] | Find Answer