Luis Milanese
Luis Milanese

Reputation: 1286

Multiple File Uploads with Symfony 3

I have had my head burning for two days and I don't know where to run to: I need to upload multiple images for an object.

Mind you, I have an entity called REALTY, for which I can upload one OR more images. So I came up with this entity:

<?php

namespace AppBundle\Entity;

use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\ORM\Mapping as ORM;

/**
 * Realty
 *
 * @ORM\Table(name="realty")
 * @ORM\Entity(repositoryClass="AppBundle\Repository\RealtyRepository")
 */
class Realty extends Timestampable
{
    /**
     * @var int
     *
     * @ORM\Column(name="id", type="integer")
     * @ORM\Id
     * @ORM\GeneratedValue(strategy="AUTO")
     */
    private $id;

    /**
     * @var string
     *
     * @ORM\Column(name="title", type="string", length=125)
     */
    private $title;

    /**
     * @var string
     *
     * @ORM\Column(name="description", type="text")
     */
    private $description;

    /**
     * @var string
     *
     * @ORM\Column(name="price", type="decimal", precision=8, scale=2)
     */
    private $price;

    /**
     * @var Advertiser
     *
     * @ORM\ManyToOne(targetEntity="Advertiser", inversedBy="realties")
     * @ORM\JoinColumn(name="adivertiser_id", referencedColumnName="id", nullable=false)
     */
    private $advertiser;

    /**
     * @var ArrayCollection
     *
     * @ORM\OneToMany(targetEntity="RealtyImage", mappedBy="realty")
     */
    private $images;

    /**
     * Constructor
     */
    public function __construct()
    {
        $this->images = new \Doctrine\Common\Collections\ArrayCollection();
    }

    /**
     * Get id
     *
     * @return int
     */
    public function getId()
    {
        return $this->id;
    }

    /**
     * Set title
     *
     * @param string $title
     *
     * @return Realty
     */
    public function setTitle($title)
    {
        $this->title = $title;

        return $this;
    }

    /**
     * Get title
     *
     * @return string
     */
    public function getTitle()
    {
        return $this->title;
    }

    /**
     * Set description
     *
     * @param string $description
     *
     * @return Realty
     */
    public function setDescription($description)
    {
        $this->description = $description;

        return $this;
    }

    /**
     * Get description
     *
     * @return string
     */
    public function getDescription()
    {
        return $this->description;
    }

    /**
     * Set price
     *
     * @param string $price
     *
     * @return Realty
     */
    public function setPrice($price)
    {
        $this->price = $price;

        return $this;
    }

    /**
     * Get price
     *
     * @return string
     */
    public function getPrice()
    {
        return $this->price;
    }

    /**
     * Set advertiser
     *
     * @param \AppBundle\Entity\Advertiser $advertiser
     *
     * @return Realty
     */
    public function setAdvertiser(\AppBundle\Entity\Advertiser $advertiser)
    {
        $this->advertiser = $advertiser;

        return $this;
    }

    /**
     * Get advertiser
     *
     * @return \AppBundle\Entity\Advertiser
     */
    public function getAdvertiser()
    {
        return $this->advertiser;
    }

    /**
     * Add image
     *
     * @param \AppBundle\Entity\RealtyImage $image
     *
     * @return Realty
     */
    public function addImage(\AppBundle\Entity\RealtyImage $image)
    {
        $this->images[] = $image;

        return $this;
    }

    /**
     * Remove image
     *
     * @param \AppBundle\Entity\RealtyImage $image
     */
    public function removeImage(\AppBundle\Entity\RealtyImage $image)
    {
        $this->images->removeElement($image);
    }

    /**
     * Get images
     *
     * @return \Doctrine\Common\Collections\Collection
     */
    public function getImages()
    {
        return $this->images;
    }
}

And then, I created another Entity to represent that one-to-many (realty has many images).

<?php

namespace AppBundle\Entity;

use Doctrine\ORM\Mapping as ORM;

/**
 * RealtyImage
 *
 * @ORM\Table(name="realty_image")
 * @ORM\Entity(repositoryClass="AppBundle\Repository\RealtyImageRepository")
 */
class RealtyImage
{
    /**
     * @var int
     *
     * @ORM\Column(name="id", type="integer")
     * @ORM\Id
     * @ORM\GeneratedValue(strategy="AUTO")
     */
    private $id;

    /**
     * @var string
     *
     * @ORM\Column(name="description", type="string", length=255)
     */
    private $description;

    /**
     * @var int
     *
     * @ORM\Column(name="realty", type="integer")
     */
    private $realty;

    /**
     * @var string
     *
     * @ORM\Column(name="path", type="string", length=255)
     */
    private $path;


    /**
     * Get id
     *
     * @return int
     */
    public function getId()
    {
        return $this->id;
    }

    /**
     * Set description
     *
     * @param string $description
     *
     * @return RealtyImage
     */
    public function setDescription($description)
    {
        $this->description = $description;

        return $this;
    }

    /**
     * Get description
     *
     * @return string
     */
    public function getDescription()
    {
        return $this->description;
    }

    /**
     * Set realty
     *
     * @param integer $realty
     *
     * @return RealtyImage
     */
    public function setRealty($realty)
    {
        $this->realty = $realty;

        return $this;
    }

    /**
     * Get realty
     *
     * @return int
     */
    public function getRealty()
    {
        return $this->realty;
    }

    /**
     * Set path
     *
     * @param string $path
     *
     * @return RealtyImage
     */
    public function setPath($path)
    {
        $this->path = $path;

        return $this;
    }

    /**
     * Get path
     *
     * @return string
     */
    public function getPath()
    {
        return $this->path;
    }
}

No problems up to here (I guess). But then I have the FormType. That's where things go wrong. If I declare it as:

<?php

namespace AppBundle\Form;

use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\FileType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;

class RealtyType extends AbstractType
{
    /**
     * @param FormBuilderInterface $builder
     * @param array $options
     */
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder
            ->add('title')
            ->add('description')
            ->add('price')
            ->add('images', FileType::class, [
                'multiple' => true,
            ])
            ->add('advertiser')
        ;
    }

    /**
     * @param OptionsResolver $resolver
     */
    public function configureOptions(OptionsResolver $resolver)
    {
        $resolver->setDefaults(array(
            'data_class' => 'AppBundle\Entity\Realty'
        ));
    }
}

I'll get the following error:

The form's view data is expected to be an instance of class Symfony\Component\HttpFoundation\File\File, but is an instance of class Doctrine\Common\Collections\ArrayCollection. You can avoid this error by setting the "data_class" option to null or by adding a view transformer that transforms an instance of class Doctrine\Common\Collections\ArrayCollection to an instance of Symfony\Component\HttpFoundation\File\File.

I'm completely lost here. I know I'm missing several concepts, but I don't know where to start looking, and I didn't even start the "handling the files" process, after I get the form to work.

Many thanks

Upvotes: 1

Views: 3840

Answers (1)

abdiel
abdiel

Reputation: 2106

That error means exactly as it said, your are trying to create a form with a property images, declared of type FileType::class, so you are saying that is a single file, and in the entity "images" is declared as a collection; the attribute multiple=>true don't resolve your problem.

For resolving this problem you must do what a lot of bundles do, create an entity containing one media or image, and then configure other entity in a OneToMany relations with the first. Then you must use embedded forms for add many images as you want.

Hope this help you

Upvotes: 3

Related Questions