title: Symfony vichuploader in form collection
body: <p>Hi I have an entity Product who containt (OneToMany) Documentation. 
  In Documentation i have a file, i use vichuploader to download it but i have a problem, when save my form i have an error who say me the filename is null (doctrine error). Is it possible to embed a vichuploader field on form collection ? 
  If somebody have an idea :) i have post my files
  Thank you</p>
  
  <p>Product.php</p>
  
  <pre><code>class Product
  {
  /**
   * @var int
   *
   * @ORM\Column(name="id", type="integer")
   * @ORM\Id
   * @ORM\GeneratedValue(strategy="AUTO")
   */
  private $id;
  
  /**
   * @var string
   *
   * @ORM\Column(name="name", type="string", length=255)
   * @Assert\Type(type="string")
   * @Assert\NotBlank()
   */
  private $name;
  
  /**
   * @ORM\OneToMany(targetEntity="AppBundle\Entity\Documentation", mappedBy="product", cascade={"persist", "remove"})
   * @ORM\JoinColumn(nullable=true)
   * @Assert\Valid()
   */
  private $documentations;
  
  /**
   * Constructor
   */
  public function __construct()
  {
      $this-&gt;documentations = new \Doctrine\Common\Collections\ArrayCollection();
  }
  
  /**
   * Get id
   *
   * @return integer
   */
  public function getId()
  {
      return $this-&gt;id;
  }
  
  /**
   * Set name
   *
   * @param string $name
   *
   * @return Product
   */
  public function setName($name)
  {
      $this-&gt;name = $name;
  
      return $this;
  }
  
  /**
   * Get name
   *
   * @return string
   */
  public function getName()
  {
      return $this-&gt;name;
  }
  
  /**
   * Add documentation
   *
   * @param \AppBundle\Entity\Documentation $documentation
   *
   * @return Product
   */
  public function addDocumentation(\AppBundle\Entity\Documentation $documentation)
  {
      $documentation-&gt;setProduct($this);
      $this-&gt;documentations[] = $documentation;
  
      return $this;
  }
  
  /**
   * Remove documentation
   *
   * @param \AppBundle\Entity\Documentation $documentation
   */
  public function removeDocumentation(\AppBundle\Entity\Documentation $documentation)
  {
      $this-&gt;documentations-&gt;removeElement($documentation);
  }
  
  /**
   * Get documentations
   *
   * @return \Doctrine\Common\Collections\Collection
   */
  public function getDocumentations()
  {
      return $this-&gt;documentations;
  }
  }
  </code></pre>
  
  <p>Documentation.php</p>
  
  <pre><code>class Documentation
  {
  /**
   * @var int
   *
   * @ORM\Column(name="id", type="integer")
   * @ORM\Id
   * @ORM\GeneratedValue(strategy="AUTO")
   */
  private $id;
  
  /**
   * @ORM\ManyToOne(targetEntity="AppBundle\Entity\Product", inversedBy="documentations")
   */
  private $product;
  
  /**
   * @var string
   *
   * @ORM\Column(name="name", type="string", length=255)
   * @Assert\Type(type="string")
   * @Assert\NotBlank()
   */
  private $name;
  
  /**
   * @Vich\UploadableField(mapping="product_documentations", fileNameProperty="fileName")
   * @Assert\NotBlank(message="Vous devez joindre un fichier", groups={"add"})
   * @Assert\File(
   *     maxSize = "20M",
   *     maxSizeMessage = "Le fichier est trop gros, il doit faire {{ limit }} {{ suffix }}"
   * )
   */
  private $file;
  
  /**
   * @ORM\Column(type="string", length=255)
   * @var string
   */
  private $fileName;
  
  /**
   * @ORM\Column(name="updated_at", type="datetime")
   * @var \DateTime $updatedAt
   */
  private $updatedAt;
  
  
  /**
   * Get id
   *
   * @return int
   */
  public function getId()
  {
      return $this-&gt;id;
  }
  /**
   * Constructor
   */
  public function __construct()
  {
      $this-&gt;groups = new \Doctrine\Common\Collections\ArrayCollection();
  }
  
  /**
   * Set name
   *
   * @param string $name
   *
   * @return Documentation
   */
  public function setName($name)
  {
      $this-&gt;name = $name;
  
      return $this;
  }
  
  /**
   * Get name
   *
   * @return string
   */
  public function getName()
  {
      return $this-&gt;name;
  }
  
  /**
   * @param File|\Symfony\Component\HttpFoundation\File\UploadedFile $file
   *
   * @return Documentation
   */
  public function setFile(File $file = null)
  {
      $this-&gt;file = $file;
      if ($file) {
          $this-&gt;updatedAt = new \DateTimeImmutable();
      }
      return $this;
  }
  
  /**
   * @return File|null
   */
  public function getFile()
  {
      return $this-&gt;file;
  }
  
  /**
   * @param string $fileName
   *
   * @return Documentation
   */
  public function setFileName($fileName)
  {
      $this-&gt;fileName = $fileName;
  
      return $this;
  }
  
  /**
   * @return string|null
   */
  public function getFileName()
  {
      return $this-&gt;fileName;
  }
  
  /**
   * Set updatedAt
   *
   * @param \DateTime $updatedAt
   *
   * @return Documentation
   */
  public function setUpdatedAt($updatedAt)
  {
      $this-&gt;updatedAt = $updatedAt;
  
      return $this;
  }
  
  /**
   * Get updatedAt
   *
   * @return \DateTime
   */
  public function getUpdatedAt()
  {
      return $this-&gt;updatedAt;
  }
  
  /**
   * Set product
   *
   * @param \AppBundle\Entity\Product $product
   *
   * @return Documentation
   */
  public function setProduct(Product $product = null)
  {
      $this-&gt;product = $product;
  
      return $this;
  }
  
  /**
   * Get product
   *
   * @return \AppBundle\Entity\Product
   */
  public function getProduct()
  {
      return $this-&gt;product;
  }
  }
  </code></pre>
  
  <p>DocumentationType.php</p>
  
  <pre><code>class DocumentationType extends AbstractType
  {
  
  /**
   * @param FormBuilderInterface $builder
   * @param array                $options
   */
  public function buildForm(FormBuilderInterface $builder, array $options)
  {
      $builder
          -&gt;add(
              'name',
              TextType::class,
              array(
                  'attr'  =&gt; array(
                      'placeholder' =&gt; 'Nom'
                  ),
                  'label' =&gt; 'Nom :'
              )
          )
          -&gt;add(
              'file',
              FileType::class,
              array(
                  'attr' =&gt; array(
                      'placeholder' =&gt; 'Fichier'
                  ),
                  'required' =&gt; false,
                  'label'        =&gt; 'Fichier :'
              )
          )
      ;
  }
  
  /**
   * @param OptionsResolver $resolver
   */
  public function configureOptions(OptionsResolver $resolver)
  {
      $resolver-&gt;setDefaults(array(
          'data_class' =&gt; Documentation::class,
          'attr' =&gt; array(
              'novalidate' =&gt; 'novalidate'
          )
      ));
  }
  }
  </code></pre>
  
  <p>ProductType.php</p>
  
  <pre><code>class ProductType extends AbstractType
  {
  /**
   * {@inheritdoc}
   */
  public function buildForm(FormBuilderInterface $builder, array $options)
  {
      $builder
          -&gt;add('name')
          -&gt;add(
              'documentations',
              CollectionType::class,
              array(
                  'entry_type' =&gt; DocumentationType::class,
                  'by_reference' =&gt; false,
                  'allow_add'    =&gt; true,
                  'allow_delete' =&gt; true,
                  'label' =&gt; 'Fichier(s) :',
                  'prototype' =&gt; true
              )
          )
      ;
  }
  
  /**
   * {@inheritdoc}
   */
  public function configureOptions(OptionsResolver $resolver)
  {
      $resolver-&gt;setDefaults(array(
          'data_class' =&gt; 'AppBundle\Entity\Product'
      ));
  }
  
  /**
   * {@inheritdoc}
   */
  public function getBlockPrefix()
  {
      return 'appbundle_product';
  }
  }
  </code></pre>
  
  <p>new.html.twig</p>
  
  <pre><code>{% extends 'base.html.twig' %}
  
  {% block body %}
  &lt;h1&gt;Product creation&lt;/h1&gt;
  
  {{ form_start(form) }}
      {{ form_row(form.name) }}
      &lt;a href="#" id="add_documentation" data-prototype-add="div#appbundle_product_documentations" class="btn btn-default btn-collection-add"&gt;
          &lt;span class="glyphicon glyphicon-plus"&gt;&lt;/span&gt; Ajouter une documentation
      &lt;/a&gt;
      {{ form_row(form.documentations) }}
      &lt;input type="submit" value="Create" /&gt;
  {{ form_end(form) }}
  
  &lt;ul&gt;
      &lt;li&gt;
          &lt;a href="{{ path('product_index') }}"&gt;Back to the list&lt;/a&gt;
      &lt;/li&gt;
  &lt;/ul&gt;
  
  &lt;script&gt;
      // On ajoute un nouveau champ à chaque clic sur le lien d'ajout.
      $('.btn-collection-add').click(function (e) {
          addDocumentation($($(this).data('prototype-add')));
          e.preventDefault(); // évite qu'un # apparaisse dans l'URL
          return false;
      });
  
      // La fonction qui ajoute un formulaire CategoryType
      function addDocumentation($container) {
          var index = $container.find(':input').length;
          // Dans le contenu de l'attribut « data-prototype », on remplace :
          // - le texte "__name__" qu'il contient par le numéro du champ
          var template = $container.attr('data-prototype').replace(/__name__/g, index);
          // On crée un objet jquery qui contient ce template
          var $prototype = $(template);
          // On ajoute le prototype modifié à la fin de la balise &lt;div&gt;
          $container.append($prototype);
          // Enfin, on incrémente le compteur pour que le prochain ajout se fasse avec un autre numéro
          index++;
      }
  
      // Ajout du listener sur le clic du lien pour effectivement supprimer l'entrée de la collection.
      $(document).on('click', '.btn-collection-delete', function (e) {
          $(this).closest('.panel').remove();
          e.preventDefault(); // évite qu'un # apparaisse dans l'URL
          return false;
      });
  &lt;/script&gt;
  
  {% endblock %}
  </code></pre>
  
  <p>NewAction on controller</p>
  
  <pre><code>public function newAction(Request $request)
  {
      $product = new Product();
      $form = $this-&gt;createForm(
          'AppBundle\Form\ProductType',
          $product,
          array(
              'validation_groups' =&gt; array('add')
          )
      );
      $form-&gt;handleRequest($request);
  
      if ($form-&gt;isSubmitted() &amp;&amp; $form-&gt;isValid()) {
          $em = $this-&gt;getDoctrine()-&gt;getManager();
          $em-&gt;persist($product);
          $em-&gt;flush();
  
          return $this-&gt;redirectToRoute('product_show', array('id' =&gt; $product-&gt;getId()));
      }
  
      return $this-&gt;render('product/new.html.twig', array(
          'product' =&gt; $product,
          'form'    =&gt; $form-&gt;createView(),
      ));
  }
  </code></pre>
  
  <p>My vich_uploader section on config.yml</p>
  
  <pre><code>vich_uploader:
  db_driver: orm
  mappings:
      product_documentations:
          uri_prefix:         /products/documentations
          upload_destination: '%kernel.root_dir%/../web/products/documentations'
          inject_on_load:     false
          delete_on_update:   true
          delete_on_remove:   true
  </code></pre>