title: Symfony3, Edit Form for collection of images
body: <p>I was following these guides: <a href="http://symfony.com/doc/current/controller/upload_file.html" rel="nofollow noreferrer" title="File upload">File upload tutorial</a> and <a href="http://symfony.com/doc/current/form/form_collections.html" rel="nofollow noreferrer" title="Collection FormType">Collection form type guide</a>. Everything was ok but "edit" action. Collection of images is shown correctly in "edit" action but when I try to submit the form (without adding any new images) i get an error.</p>
  
  <blockquote>
    <p>Call to a member function guessExtension() on null</p>
  </blockquote>
  
  <p>Which means that images in a collection got null values, instead of UploadedFile.</p>
  
  <p>I've searched a lot and read many of sof questions about handling collection of images with doctrine but still no clue.</p>
  
  <p>Questions like:</p>
  
  <ul>
  <li><a href="https://stackoverflow.com/questions/22898884/symfony2-edit-form-upload-picture">Symfony2, Edit Form, Upload Picture</a></li>
  <li><a href="https://stackoverflow.com/questions/15553060/howto-handle-edit-forms-with-filetype-inputs-in-symfony2">howto handle edit forms with FileType inputs in symfony2</a></li>
  <li><a href="https://stackoverflow.com/questions/23456108/multiple-onetomany-entities-form-generation-with-symfony2-and-file-upload">Multiple (oneToMany) Entities form generation with symfony2 and file upload</a></li>
  </ul>
  
  <p>So the question is how can u handle edit images? Removal is needed too.</p>
  
  <p><strong>Controller edit action</strong></p>
  
  <pre><code>public function editAction(Request $request, Stamp $stamp)
  {
      if (!$stamp) {
          throw $this-&gt;createNotFoundException('No stamp found');
      }
      $form = $this-&gt;createForm(AdminStampForm::class, $stamp);
  
      $form-&gt;handleRequest($request);
      if ($form-&gt;isSubmitted() &amp;&amp; $form-&gt;isValid()) {
          /** @var Stamp $stamp */
          $stamp = $form-&gt;getData();
          foreach ($stamp-&gt;getImages() as $image) {
              $fileName = md5(uniqid()) . '.' . $image-&gt;getName()-&gt;guessExtension();
              /** @var $image StampImage */
              $image-&gt;getName()-&gt;move(
                  $this-&gt;getParameter('stamps_images_directory'),
                  $fileName
              );
              $image-&gt;setName($fileName)-&gt;setFileName($fileName);
          }
          $em = $this-&gt;getDoctrine()-&gt;getManager();
          $em-&gt;persist($stamp);
          $em-&gt;flush();
  
          $this-&gt;addFlash('success', 'Successfully edited a stamp!');
  
          return $this-&gt;redirectToRoute('admin_stamps_list');
      }
  
      return [
          'form' =&gt; $form-&gt;createView(),
      ];
  }
  </code></pre>
  
  <p><strong>Entity</strong></p>
  
  <pre><code>&lt;?php
  
  namespace AppBundle\Entity;
  
  use Doctrine\Common\Collections\ArrayCollection;
  use Doctrine\ORM\Mapping as ORM;
  use Doctrine\ORM\Mapping\JoinColumn;
  use Doctrine\ORM\Mapping\JoinTable;
  use Doctrine\ORM\Mapping\ManyToMany;
  
  /**
   * Class Stamp
   * @package AppBundle\Entity
   *
   *
   * @ORM\Entity(repositoryClass="AppBundle\Repository\StampRepository")
   * @ORM\Table(name="stamp")
   * @ORM\HasLifecycleCallbacks()
   */
  class Stamp
  {
      /**
       * @ORM\Id
       * @ORM\GeneratedValue(strategy="AUTO")
       * @ORM\Column(type="integer")
       */
      private $id;
  
      //      * @ORM\OneToMany(targetEntity="AppBundle\Entity\StampImage", mappedBy="id", cascade={"persist", "remove"})
  
      /**
       * @ManyToMany(targetEntity="AppBundle\Entity\StampImage", cascade={"persist"})
       * @JoinTable(name="stamps_images",
       *      joinColumns={@JoinColumn(name="stamp_id", referencedColumnName="id")},
       *      inverseJoinColumns={@JoinColumn(name="image_id", referencedColumnName="id", unique=true)}
       *      )     */
      private $images;
  
      /**
       * @ORM\Column(type="datetime")
       */
      private $createdAt;
  
      /**
       * @ORM\Column(type="datetime")
       */
      private $updatedAt;
  
      public function __construct()
      {
          $this-&gt;images = new ArrayCollection();
      }
  
      /**
       * @return mixed
       */
      public function getId()
      {
          return $this-&gt;id;
      }
  
      public function addImage(StampImage $image)
      {
          $this-&gt;images-&gt;add($image);
      }
  
      public function removeImage(StampImage $image)
      {
          $this-&gt;images-&gt;removeElement($image);
      }
  
      /**
       * @return mixed
       */
      public function getImages()
      {
          return $this-&gt;images;
      }
  
      /**
       * @param mixed $images
       *
       * @return $this
       */
      public function setImages($images)
      {
          $this-&gt;images = $images;
  
          return $this;
      }
  
      /**
       * @return mixed
       */
      public function getCreatedAt()
      {
          return $this-&gt;createdAt;
      }
  
      /**
       * @param mixed $createdAt
       *
       * @return Stamp
       */
      public function setCreatedAt($createdAt)
      {
          $this-&gt;createdAt = $createdAt;
  
          return $this;
      }
  
      /**
       * @return mixed
       */
      public function getUpdatedAt()
      {
          return $this-&gt;updatedAt;
      }
  
      /**
       * @param mixed $updatedAt
       *
       * @return Stamp
       */
      public function setUpdatedAt($updatedAt)
      {
          $this-&gt;updatedAt = $updatedAt;
  
          return $this;
      }
  
      /**
       * @ORM\PrePersist
       * @ORM\PreUpdate
       */
      public function updateTimestamps()
      {
          $this-&gt;setUpdatedAt(new \DateTime('now'));
  
          if (null == $this-&gt;getCreatedAt()) {
              $this-&gt;setCreatedAt(new \DateTime());
          }
      }
  }
  </code></pre>
  
  <p><strong>StampImage entity</strong></p>
  
  <pre><code>&lt;?php
  
  namespace AppBundle\Entity;
  
  use Doctrine\ORM\Mapping as ORM;
  use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity as UniqueEntity;
  use Symfony\Component\Validator\Constraints as Assert;
  
  /**
   * Class StampImage
   * @package AppBundle\Entity
   *
   * @ORM\Entity()
   * @ORM\Table(name="stamp_image")
   * @UniqueEntity(fields={"name"}, message="This file name is already used.")
   */
  class StampImage
  {
      /**
       * @ORM\Id
       * @ORM\GeneratedValue(strategy="AUTO")
       * @ORM\Column(type="integer")
       */
      private $id;
  
      /**
       * @return mixed
       */
      public function getId()
      {
          return $this-&gt;id;
      }
  
      private $name;
  
      /**
       * @ORM\Column(type="string")
       */
      private $fileName;
  
      /**
       * @return mixed
       */
      public function getFileName()
      {
          return $this-&gt;fileName;
      }
  
      /**
       * @param mixed $fileName
       *
       * @return StampImage
       */
      public function setFileName($fileName)
      {
          $this-&gt;fileName = $fileName;
  
          return $this;
      }
  
      /**
       * @return mixed
       */
      public function getName()
      {
          return $this-&gt;name;
      }
  
      /**
       * @param mixed $name
       *
       * @return StampImage
       */
      public function setName($name)
      {
          $this-&gt;name = $name;
  
          return $this;
      }
  
  }
  </code></pre>
  
  <p><strong>Main entity form</strong></p>
  
  <pre><code>&lt;?php
  
  namespace AppBundle\Form;
  
  use AppBundle\Entity\Stamp;
  use Symfony\Component\Form\AbstractType;
  use Symfony\Component\Form\Extension\Core\Type\CollectionType;
  use Symfony\Component\Form\FormBuilderInterface;
  use Symfony\Component\OptionsResolver\OptionsResolver;
  
  class AdminStampForm extends AbstractType
  {
      public function buildForm(FormBuilderInterface $builder, array $options)
      {
          $builder
              -&gt;add('images', CollectionType::class, [
                  'entry_type' =&gt; AdminStampImageForm::class,
                  'allow_add' =&gt; true,
                  'allow_delete' =&gt; true,
                  'by_reference' =&gt; false,
              ])
          ;
  
      }
  
      public function configureOptions(OptionsResolver $resolver)
      {
          $resolver-&gt;setDefaults([
              'data_class' =&gt; Stamp::class,
          ]);
      }
  
      public function getName()
      {
          return 'app_bundle_admin_stamp_form';
      }
  }
  </code></pre>
  
  <p><strong>Image form type</strong></p>
  
  <pre><code>&lt;?php
  
  namespace AppBundle\Form;
  
  use AppBundle\Entity\StampImage;
  use Symfony\Component\Form\AbstractType;
  use Symfony\Component\Form\Extension\Core\Type\FileType;
  use Symfony\Component\Form\FormBuilderInterface;
  use Symfony\Component\OptionsResolver\OptionsResolver;
  
  class AdminStampImageForm extends AbstractType
  {
      public function buildForm(FormBuilderInterface $builder, array $options)
      {
          $builder
              -&gt;add('name', FileType::class, [
                  'image_name' =&gt; 'fileName',
                  'label' =&gt; false,
                  'attr' =&gt; [
                  ],
              ]);
      }
  
      public function configureOptions(OptionsResolver $resolver)
      {
          $resolver-&gt;setDefaults([
              'data_class' =&gt; StampImage::class,
              'required' =&gt; false
          ]);
      }
  
      public function getBlockPrefix()
      {
          return 'app_bundle_admin_stamp_image_form';
      }
  }
  </code></pre>
  
  <p><strong>FileType extension</strong></p>
  
  <pre><code>- extends 'form_div_layout.html.twig'
  
  - block file_widget
    - spaceless
      - if image_url is not null
        %img{:src =&gt; "#{image_url}"}
        %div{:style =&gt; "display: none;"}
          #{block('form_widget')}
      - else
        #{block('form_widget')}
  </code></pre>