3

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,
        ]);
    }
}

3 Answers 3

2

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;
    }
}

Sign up to request clarification or add additional context in comments.

Comments

1

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])

14 Comments

Catchable Fatal Error: Argument 1 passed to AppBundle\Entity\Product::attachPicture() must be an instance of AppBundle\Entity\UploadedFile, instance of Symfony\Component\HttpFoundation\File\UploadedFile given
Actually i fixed this eror, but i am getting Error 500
Check that in top of the Product entity the use you have is not use AppBundle\Entity\UploadedFile, it must be use Symfony\Component\HttpFoundation\File\UploadedFile
Can you provide the trace error of the symfony toolbar?
It looks like a form problem... try changing ->add('attachPictures', 'file', ['multiple'=>true, 'required'=>false]) for ->add('attachPictures', FileType::class, ['multiple'=>true, 'required'=>false]) or ->add('attachPictures', VichFileType::class, ['multiple'=>true, 'required'=>false])
|
0

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

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.