2

I try to allowed the multiple upload with the bundle VichUploader. On the project, i have a class Theater which own a main image but also a number of secondary images (Collection of images). Actually each secondary images is a Ressource. So a Theater have onetomany Resources and a Resource is connected to one theater.

But when i try to create i can access to my form but i have an error when i try to save which is :

Expected argument of type "AppBundle\Entity\Resources", "AppBundle\Entity\Theatre" given 

Here this is my class Theater with only details for multiple upload :

namespace AppBundle\Entity;

use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\HttpFoundation\File\File;
use Symfony\Component\HttpFoundation\File\UploadedFile;
use Symfony\Component\Validator\Constraints as Assert;
use Symfony\Component\Validator\Context\ExecutionContextInterface;
use Vich\UploaderBundle\Mapping\Annotation as Vich;

/**
* Theatre
*
* @ORM\Table(name="theatre")
* @ORM\Entity
* @Vich\Uploadable
*/
class Theatre
{

/**
 *  @var ArrayCollection
 * @ORM\OneToMany(targetEntity="Resources", mappedBy="theatre", cascade={"persist", "remove"}, orphanRemoval=true)
 */
private $images;

// ..

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

// MultiUpload
/**
 * @return ArrayCollection
 */
public function getImages()
{
    return $this->images;
}

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

public function getAttachImages()
{
    return null;
}

/**
 * @param array $files
 *
 * @return array
 */
public function setAttachImages(array $files=array())
{
    if (!$files) return [];
    foreach ($files as $file) {
        if (!$file) return [];
        $this->attachImages($file);
    }
    return [];
}

/**
 * @param UploadedFile|null $file
 */
public function attachImages(UploadedFile $file=null)
{
    if (!$file) {
        return;
    }
    $picture = new Resources();
    $picture->setImage($file);
    $this->addImage($picture);
}



/**
 * Add image.
 *
 * @param \AppBundle\Entity\Resources $image
 *
 *
 */
public function addImage(\AppBundle\Entity\Resources $image)
{
    $image->setTheatre($this);
    //$this->images->add($image);
    $this->images[] = $image;

    //return $this;
}

/**
 * Remove image.
 *
 * @param \AppBundle\Entity\Resources $image
 *
 *
 */
public function removeImage(\AppBundle\Entity\Resources $image)
{
    $image->setTheatre(null);
    $this->images->removeElement($image);
   // return $this->images->removeElement($image);
}

Then my class Resources :

namespace AppBundle\Entity;

use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\HttpFoundation\File\File;
use Symfony\Component\HttpFoundation\File\UploadedFile;
use Symfony\Component\Validator\Constraints as Assert;
use Symfony\Component\Validator\Context\ExecutionContextInterface;
use Vich\UploaderBundle\Mapping\Annotation as Vich;

/**
* Resources
*
* @ORM\Table(name="resources")
* @ORM\Entity
* @Vich\Uploadable
*/
class Resources
{

 /**
 * @var Theatre
 * @ORM\ManyToOne(targetEntity="Theatre", inversedBy="images")
 */
private $theatre;

/**
 * @Vich\UploadableField(mapping="uploads_image", fileNameProperty="url")
 * @Assert\File(
 *      mimeTypes = {"image/png", "image/jpeg", "image/jpg"},
 *      mimeTypesMessage = "Please upload a valid valid IMAGE"
 * )
 *
 *
 * @var File $image
 */
protected $image;

/**
 * @ORM\Column(type="string", length=255, name="url")
 *
 * @var array $url
 */
protected $url;


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

public function getImage()
{
    return $this->image;
}

// ..

  /**
 * Set theatre.
 *
 * @param \AppBundle\Entity\Theatre $theatre
 *
 * @return Resources
 */
public function setTheatre(\AppBundle\Entity\Theatre $theatre)
{
    $this->theatre = $theatre;

    return $this;
}

/**
 * Get theatre.
 *
 * @return \AppBundle\Entity\Theatre
 */
public function getTheatre()
{
    return $this->theatre;
}

/**
 * Set url.
 *
 * @param string $url
 *
 * @return Resources
 */
public function setUrl($url)
{
    $this->url = $url;

    return $this;
}

/**
 * Get url.
 *
 * @return array
 */
public function getUrl()
{
    return $this->url;
}

Then I add to the builder :

namespace AppBundle\Form\Collection;

use AppBundle\Entity\Theatre;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\FileType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Vich\UploaderBundle\Form\Type\VichFileType;


class TheatreImages extends AbstractType{
/**
 * @param FormBuilderInterface $builder
 * @param array                $options
 */
public function buildForm(FormBuilderInterface $builder, array $options)
{
    $builder
        ->add('attachImages', FileType::class, ['multiple'=>true, 'required'=>false])
    ;


}

/**
 * @param OptionsResolver $resolver
 */
public function configureOptions(OptionsResolver $resolver)
{
    $resolver->setDefaults(array(
        'data_class' => Theatre::class,
    ));
}

/**
 * @return string
 */
public function getName()
{
    return 'app_theatreImages';
}
}

I add my config with the bundle easyAdminBundle:

easy_admin:
   entities:
    Theatre:
        class: AppBundle\Entity\Theatre
        list:
            title: 'Liste des théâtres'
            fields:
                - 'Name'
                - 'adress'
                - 'Metro station'
                - { property: 'Main image', type: 'image',  template: 'theatreFile.html.twig',  base_path: '%app.path.theatre_images%' }

        new:
            title: 'Création théâtre'
            fields:
                - { type: 'section', label: 'Information du théâtre' }
                - {property: 'name', label: 'Nom'}
                - {property: 'number_of_seats', type: 'integer', label: 'Nombre de sièges'}
                - {property: 'about', label: 'description'}
                - { property: 'imageFile', type: 'vich_file', label: 'image', type_options: { required: false}}
                - {property: 'images', type: 'collection', type_options: {entry_type: 'AppBundle\Form\Collection\TheatreImages', by_reference: false}}
                - { type: 'section', label: 'Localisation du théâtre' }
                - {property: 'adress', label: 'adresse'}
                - {property: 'metro_station', label: 'Station de métro'}
                - {property: 'location_coordinates', label: 'Coordonnées'}
        edit:
            title: "Édition théâtre"
            fields:
                - { type: 'section', label: 'Information du théâtre' }
                - {property: 'name', label: 'Nom'}
                - {property: 'number_of_seats', type: 'integer', label: 'Nombre de sièges'}
                - {property: 'about', label: 'description'}
                - { property: 'imageFile', type: 'vich_file', label: 'image', type_options: { required: false}}
                - {property: 'images', type: 'collection', type_options: {entry_type: 'AppBundle\Form\Collection\TheatreImages', by_reference: false}}
                - { type: 'section', label: 'Localisation du théâtre' }
                - {property: 'adress', label: 'adresse'}
                - {property: 'metro_station', label: 'Station de métro'}
                - {property: 'location_coordinates', label: 'Coordonnées'}

Thanks in advance.

2
  • Could you please link your controller Commented May 17, 2018 at 5:25
  • @ShaithyaKannan Actually i use this Bundle with EasyAdmin. I just added the config about this. I did not use a custom controller Commented May 17, 2018 at 7:42

2 Answers 2

5

I have created a solution for a very useful bundle [VichUploader] which is missing the functionality of multiple uploads and it works on every Symfony version, I have created on [Symfony] 5.2. It's on OneToMany relation and it works fine. So I have used CollectionType and [VichFileType] in my custom forms and little trick in my controller, Here the code and to see all the project, you can find it in my GitHub [a link] https://github.com/malek-laatiri

Admission.php

class Admission
{
    /**
     * @ORM\OneToMany(targetEntity=Diplome::class, mappedBy="admission")
     */
    private $diplomes;
    /**
     * @return Collection|Diplome[]
     */
    public function getDiplomes(): Collection
    {
        return $this->diplomes;
    }

    public function addDiplome(Diplome $diplome): self
    {
        if (!$this->diplomes->contains($diplome)) {
            $this->diplomes[] = $diplome;
            $diplome->setAdmission($this);
        }

        return $this;
    }

    public function removeDiplome(Diplome $diplome): self
    {
        if ($this->diplomes->removeElement($diplome)) {
            // set the owning side to null (unless already changed)
            if ($diplome->getAdmission() === $this) {
                $diplome->setAdmission(null);
            }
        }

        return $this;
    }
}

Diplome.php

<?php

namespace App\Entity;

use App\Repository\DiplomeRepository;
use Doctrine\ORM\Mapping as ORM;
use Vich\UploaderBundle\Mapping\Annotation as Vich;


/**
 * @ORM\Entity(repositoryClass=DiplomeRepository::class)
 * @Vich\Uploadable
 */
class Diplome
{
    /**
     * @ORM\Id
     * @ORM\GeneratedValue
     * @ORM\Column(type="integer")
     */
    private $id;

    /**
     * @ORM\ManyToOne(targetEntity=Admission::class, inversedBy="diplomes",cascade={"persist","remove"})
     */
    private $admission;

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

    /**
     * @Vich\UploadableField(mapping="product_image", fileNameProperty="name")
     * @var File
     */
    private $file;

    public function getId(): ?int
    {
        return $this->id;
    }

    public function getAdmission(): ?Admission
    {
        return $this->admission;
    }

    public function setAdmission(?Admission $admission): self
    {
        $this->admission = $admission;

        return $this;
    }

    public function getName(): ?string
    {
        return $this->name;
    }

    public function setName(string $name): self
    {
        $this->name = $name;

        return $this;
    }

    public function getFile()
    {
        return $this->file;
    }

    public function setFile( $file)
    {
        $this->file = $file;

        return $this;
    }
}

AdmissionType.php

<?php

namespace App\Form;

use App\Entity\Admission;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\CollectionType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Vich\UploaderBundle\Form\Type\VichFileType;

class Admission1Type extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder
            ->add('diplomes', CollectionType::class, [
                'entry_type' => DiplomeType::class,
                'allow_add' => true,
                'allow_delete' => true,
                'required' => false,
                'label'=>false,
                'by_reference' => false,
                'disabled' => false,
            ]);
    }

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

DiplomeType.php

<?php

namespace App\Form;

use App\Entity\Diplome;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Vich\UploaderBundle\Form\Type\VichFileType;

class DiplomeType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder
            ->add('file',VichFileType::class)
        ;
    }

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

_form.html.twig

<ul id="diplomes-fields-list"
    data-prototype="{{ form_widget(form.diplomes.vars.prototype)|e }}"
    data-widget-tags="{{ '<li></li>'|e }}"
    data-widget-counter="{{ form.diplomes|length }}">
    {% for emailField in form.diplomes %}
        <li>
            {{ form_errors(emailField) }}
            {{ form_widget(emailField) }}
        </li>
    {% endfor %}
</ul>
<button type="button"
        class="add-another-collection"
        data-list-selector="#diplomes-fields-list">Add another email
</button>

script.js

jQuery(document).ready(function () {
    jQuery('.add-another-collection').click(function (e) {
        var list = $("#diplomes-fields-list");
        var counter = list.data('widget-counter') | list.children().length;
        var newWidget = list.attr('data-prototype');
        newWidget = newWidget.replace(/__name__/g, counter);
        counter++;
        list.data('widget-counter', counter);

        var newElem = jQuery(list.attr('data-widget-tags')).html(newWidget);
        newElem.appendTo(list);
        newElem.append('<a href="#" class="remove-tag" style="color: darkred">remove</a>');
        $('.remove-tag').click(function(e) {
            e.preventDefault();

            $(this).parent().remove();

        });
    });
});

Controller.php

$admission = new Admission();
        $form = $this->createForm(Admission1Type::class, $admission);
        $form->handleRequest($request);
        $entityManager = $this->getDoctrine()->getManager();
        if ($form->isSubmitted() && $form->isValid()) {
            foreach ($form->getData()->getNotes() as $dip){
                $entityManager->persist($dip);
                $admission->addNote($dip);
            }
            $entityManager->persist($admission);
            $entityManager->flush();
Sign up to request clarification or add additional context in comments.

1 Comment

Hi Malek :) You code is quite good, I'll love to have a look at your github repository! Possible to send a link ?
1

Vihcuploader not support multiple file uploads well, you have to create a custom form type to handle it maybe this solution would help:

github user soluton

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.