SUBSCRIBE VIA RSS


Subscribe to our feed

Status Updates

  • First iPhone app nearly complete! I've been waiting for 3 weeks for Apple to finish "conducting company identity verification."
    3 days ago
  • Rainy day! Staying indoors. Creating computerized players in a simulation game that models cooperation in the work place.
    75 days ago
  • Up at 8AM, with a hot cup of coffee brought back from Cafe Lola in Ann Arbor. Will I succeed in putting in 8 billable hours in one day?
    101 days ago
  • full scale irish band outside my window playing the pipes for the past 20 minutes. pretty fun.
    109 days ago
  • listening to Kristin on Maxim Radio!
    115 days ago
  • back from an amazing meal at ouest with even more amazing company.
    116 days ago
  • wondering how so many online tshirt companies stay in business--ones that sell shirts with funny messages, i never see them in real life
    122 days ago

Topics

TWITTER

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 under 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 in  Uncategorized   |     |  delicious  Digg

14 Responses to “Symfony 1.2: upload a file inside an embedded form”

  1. But this solution don’t work if main form have file field :(
    Any things about this?

    By eugeneis on Feb 11, 2009

  2. @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

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

  4. just awesome =) thanks so much !

    By country trek on Feb 16, 2009

  5. It works perfectly, good job !

    By BIOT on Feb 25, 2009

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

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

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

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

    By rekarnar on Apr 22, 2009

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

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

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

    By leonardo lazzaro on Jun 13, 2009

2 Trackback(s)

  1. Dec 25, 2008: Symfony Form: ?????????? ????????????? ???? ??? ?????? ? ?? | ?????? ??? ????????????
  2. Jan 23, 2009: Stereo Interactive & Design » Uploading a file with Symfony 1.2

Post a Comment

*
To prove you're a person (not a spam script), type the security word shown in the picture. Click on the picture to hear an audio file of the word.
Click to hear an audio file of the anti-spam word