Jakub Staněk
Jakub Staněk

Reputation: 65

symfony multiple upload vichuploadbundle

im looking for multiple upload in symfony using vichuploadbundle. I also have working uploader but still for only one file. I cant find answers which solve my problem.I also tried many tutorials, but nothing worked for me. In their documentation i couldnt find answer. What should i do?

error:

Expected argument of type "Symfony\Component\HttpFoundation\File\File", "array" given

My entity:

 /**
 * @ORM\Entity
 * @Vich\Uploadable
 */
class Product
{
    /**
     * @ORM\Id
     * @ORM\Column(type="integer")
     * @ORM\GeneratedValue(strategy="AUTO")
     */
    private $id;

    /**
     * @ORM\Column(type="string", length=50, nullable=true)
     * @Assert\NotBlank()
     * @Assert\Length(
     *     min=5,
     *     minMessage="Title is too short!",
     *     max=50,
     *     maxMessage="Title is too long!"
     * )
     */
    private $title;

    /**
     * @ORM\ManyToOne(targetEntity="AppBundle\Entity\User")
     * @ORM\JoinColumn(nullable=false)
     */
    private $author;

    /**
     * @ORM\Column(type="string", length=150, nullable=true)
     * @Assert\NotBlank()
     * @Assert\Length(
     *     min=5,
     *     minMessage="Perex is too short!",
     *     max=100,
     *     maxMessage="Perex is too long!"
     * )
     */
    private $perex;

    /**
     * @ORM\Column(type="text", length=500, nullable=true)
     * @Assert\NotBlank()
     */
    private $text;

    /**
     * @var ArrayCollection
     * @ORM\OneToMany(targetEntity="AppBundle\Entity\ProductPicture", mappedBy="product", cascade={"all"}, orphanRemoval=true)
     */
    private $pictures;

    public function __construct()
    {
        $this->pictures = new ArrayCollection();
    }

    /**
     * @return ArrayCollection
     */
    public function getPictures()
    {
        return $this->pictures;
    }

    /**
     * @param ArrayCollection $pictures
     */
    public function setPictures($pictures)
    {
        $this->pictures = $pictures;
    }

    public function getAttachPictures()
    {
        return null;
    }

    public function setAttachPictures(array $files=array())
    {
        if (!$files) return [];
        foreach ($files as $file) {
            if (!$file) return [];
            $this->attachPicture($file);
        }
        return [];
    }

    public function attachPicture(UploadedFile $file=null)
    {
        if (!$file) {
            return;
        }
        $picture = new ProductPicture();
        $picture->setImageFile($file);
        $this->addPicture($picture);
    }

    public function addPicture(ProductPicture $picture)
    {
        $picture->setProduct($this);
        $this->pictures->add($picture);
    }

    public function removePicture(ProductPicture $picture)
    {
        $picture->setProduct(null);
        $this->pictures->removeElement($picture);
    }
}

ProductPicture:

/**
 * @ORM\Entity
 * @Vich\Uploadable
 */
class ProductPicture
{
    /**
     * @ORM\Id
     * @ORM\Column(type="integer")
     * @ORM\GeneratedValue(strategy="AUTO")
     */
    private $id;

    /**
     * @var Product
     * @ORM\ManyToOne(targetEntity="AppBundle\Entity\Product", inversedBy="pictures")
     */
    private $product;

    /**
     *
     * @Vich\UploadableField(mapping="product_image", fileNameProperty="imageName", size="imageSize")
     * @Assert\File(
     *     maxSize = "1024k",
     *     mimeTypes = {"image/png", "image/jpeg", "image/jpg"},
     *     mimeTypesMessage = "Please upload a valid valid IMAGE"
     * )
     *
     * @var File
     */
    private $imageFile;

    /**
     * @ORM\Column(type="string", length=255, nullable=true)
     *
     * @var array
     */
    private $imageName;

    /**
     *
     * @param File|\Symfony\Component\HttpFoundation\File\UploadedFile $image
     *
     * @return Product
     */
    public function setImageFile(File $image = null)
    {
        $this->imageFile = $image;
    }

    public function getImageFile()
    {
        return $this->imageFile;
    }

    public function setProduct(Product $product)
    {
        $this->product = $product;
    }

    public function getProduct()
    {
        return $this->product;
    }

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

    /**
     * @param mixed $id
     */
    public function setId($id)
    {
        $this->id = $id;
    }

    /**
     * @return array
     */
    public function getImageName()
    {
        return $this->imageName;
    }

    /**
     * @param array $imageName
     */
    public function setImageName($imageName)
    {
        $this->imageName = $imageName;
    }

}

Form:

class ProductType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder
            ->add('title')
            ->add('perex')
            ->add('text')
            ->add('attachPictures', FileType::class, ['multiple'=>true, 'required'=>false])
        ;
    }

    public function configureOptions(OptionsResolver $resolver)
    {
        $resolver->setDefaults([
            'data_class' => Product::class,
        ]);
    }
}

Upvotes: 3

Views: 2521

Answers (3)

Marcin Harasim
Marcin Harasim

Reputation: 75

Symfony 4 working solution.

Quite more simple. Just tidy your entities, create form and form transformer

// src/Entity/Product.php
 /**
 * @ORM\Entity
 * @Vich\Uploadable
 */
class Product
{
    ...

    /**
     * @var Collection|ProductPicture[]
     * @ORM\OneToMany(targetEntity="AppBundle\Entity\ProductPicture", mappedBy="product", cascade={"persist", "remove"}, orphanRemoval=true)
     */
    private $pictures;

    public function __construct()
    {
        $this->pictures = new ArrayCollection();
    }

    /**
     * @return Collection|ProductPicture[]
     */
    public function getPictures(): Collection
    {
        return $this->pictures;
    }

    public function addPicture(ProductPicture $picture): self
    {
        if (!$this->pictures->contains($picture) {
            $this->pictures->add($picture);
            $picture->setProduct($this);
        }

        return $this;
    }

    public function removePicture(ProductPicture $picture): self
    {
        if ($this->pictures->contains($picture) {
            $this->pictures->removeElement($picture);
            if ($picture->getProduct() === $this) {
                $picture->setProduct(null);
            }
        }

        return $this;
    }

    ...
}
// src/Entity/ProductPicture.php
/**
 * @ORM\Entity
 * @Vich\Uploadable
 */
class ProductPicture
{
    ...

    /**
     * @var Product
     * @ORM\ManyToOne(targetEntity="AppBundle\Entity\Product", inversedBy="pictures")
     * @ORM\JoinColumn(nullable=false)
     */
    private $product;

    /**
     *
     * @Vich\UploadableField(mapping="product_image", fileNameProperty="imageName", size="imageSize")
     * @Assert\File(
     *     maxSize = "1024k",
     *     mimeTypes = {"image/png", "image/jpeg", "image/jpg"},
     *     mimeTypesMessage = "Please upload a valid valid IMAGE"
     * )
     *
     * @var File
     */
    private $imageFile;

    ...

    /**
     * @param File|\Symfony\Component\HttpFoundation\File\UploadedFile $image
     *
     * @return Product
     */
    public function setImageFile(?File $image = null)
    {
        $this->imageFile = $image;

        if (null !== $image) {
            // It is required that at least one field changes if you are using doctrine
            // otherwise the event listeners won't be called and the file is lost
            $this->updatedAt = new DateTimeImmutable();
        }
    }

    public function getImageFile()
    {
        return $this->imageFile;
    }

    public function setProduct(Product $product)
    {
        $this->product = $product;
    }

    public function getProduct()
    {
        return $this->product;
    }

    ...
}

Then you need to create simple ProductType form:

// src/Form/ProductForm.php
class PictureType extends AbstractType
{

    public function buildForm(FormBuilderInterface $builder, array $options): void
    {
        $builder
            ->add($builder->create(
                'pictures',
                FileType::class,
                [
                    'required' => false,
                    'multiple' => true,
                ]
            )->addModelTransformer(new FilesToPicturesTransformer()))
            ->add(...);
    }

    public function configureOptions(OptionsResolver $resolver): void
    {
        $resolver->setDefaults([
            'data_class' => Product::class,
        ]);
    }

and FilesToPicturesTransformer (data transformer):

// src/Form/FilesToPicturesTransformer.php
class FilesToPicturesTransformer implements DataTransformerInterface
{
    /**
     * {@inheritdoc}
     */
    public function transform($value): void
    {
        // don't need this if you build API
    }

    /**
     * {@inheritdoc}
     */
    public function reverseTransform($value): ArrayCollection
    {
        $pictures = new ArrayCollection();

        foreach ($value as $file) {
            $picture = (new ProductPicture())
                ->setImageFile($file);
            if (!$pictures->contains($picture)) {
                $pictures->add($picture);
            }
        }

        return $pictures;
    }
}

Upvotes: 2

Enumus
Enumus

Reputation: 66

You should build the file part as an entity

/**
 * @ORM\Entity
 * @Vich\Uploadable
 */
class ProductPicture
{
    /**
     * @ORM\Id
     * @ORM\Column(type="integer")
     * @ORM\GeneratedValue(strategy="AUTO")
     */
    private $id;

    /**
     * @var Product
     * @ORM\ManyToOne(targetEntity="path\to\your\entity\Product", inversedBy="pictures")
     */
    private $product;

    /**
     *
     * @Vich\UploadableField(mapping="product_image", fileNameProperty="imageName", size="imageSize")
     * @Assert\File(
     *     maxSize = "1024k",
     *     mimeTypes = {"image/png", "image/jpeg", "image/jpg"},
     *     mimeTypesMessage = "Please upload a valid valid IMAGE"
     * )
     *
     * @var File
     */
    private $imageFile;

    /**
     * @ORM\Column(type="string", length=255, nullable=true)
     *
     * @var array
     */
    private $imageName;

    /**
     *
     * @param File|\Symfony\Component\HttpFoundation\File\UploadedFile $image
     *
     * @return Product
     */
    public function setImageFile(File $image = null)
    {
        $this->imageFile = $image;
    }

    public function getImageFile()
    {
         return $this->imageFile;
    }

    public function setProduct(Product $product)
    {
        $this->product = $product;
    }

    public function getProduct()
    {
         return $this->product;
    } 
}

And in your Product entity...

    /**
     * @var ArrayCollection
     * @ORM\OneToMany(targetEntity="path\to\your\entity\ProductPicture", mappedBy="product", cascade={"all"}, orphanRemoval=true)
     */
    private $pictures;

    public function __construct()
    {
        $this->pictures = new ArrayCollection();
    }

    /**
     * @return ArrayCollection
     */
    public function getPictures()
    {
        return $this->pictures;
    }

    /**
     * @param ArrayCollection $pictures
     */
    public function setPictures($pictures)
    {
        $this->pictures = $pictures;
    }

    public function getAttachPictures()
    {
        return null;
    }

    public function setAttachPictures(array $files=array())
    {
        if (!$files) return [];
        foreach ($files as $file) {
            if (!$file) return [];
            $this->attachPicture($file);
        }
        return [];
    }

    public function attachPicture(UploadedFile $file=null)
    {
        if (!$file) {
            return;
        }
        $picture = new ProductPicture();
        $picture->setImageFile($file);
        $this->addPicture($picture);
    }

    public function addPicture(ProductPicture $picture)
    {
        $picture->setProduct($this);
        $this->pictures->add($picture);
    }

    public function removePicture(ProductPicture $picture)
    {
       $picture->setProduct(null);
       $this->pictures->removeElement($picture);
   }

And finally in the form use...

->add('attachPictures', FileType::class, ['multiple'=>true, 'required'=>false])

Upvotes: 1

Michał G
Michał G

Reputation: 2302

I can't help you with this upload bundle, but i can show you how it's working in my case in more simple way (using simple requests, not some fancy bundle)

here https://gist.github.com/poznet/e3955a48fea01d8640dafbd966c53a83

you have

  • form in view
  • controller code
  • trait that is imported in service entity

This trait it's a little mess (no attachemt size/type check etc ) , but it works.

If you have any questions fell free to ask.

PS in your case if you uploading multiple files you get array , not file class :D

Upvotes: 0

Related Questions