EasyCorp / EasyAdminBundle

EasyAdmin is a fast, beautiful and modern admin generator for Symfony applications.
MIT License
4.06k stars 1.02k forks source link

Multiple file/images upload - OneupUploaderBundle #825

Closed mkalisz77 closed 8 years ago

mkalisz77 commented 8 years ago

Is there any possibility to manage upload multiple files? Maybe this functionality could be added to documentation.

There are some case fg. Gallery/Images in gallery with One2Many relation when it will be nice to have one form for that in EasyAdmin with possibility to manage multiple files at once.

I think that this bundle OneupUploaderBundle is very nice to do this job. Maybe somebody use this bundle with EasyAdmin and can share with community how to start ?

OneupUploaderBundle integrate many external Javascript uploaders: See examples here: http://www.plupload.com/examples/ http://blueimp.github.io/jQuery-File-Upload/ http://fineuploader.com/demos.html

Maybe it can be added to roadmap fo EasyAdmin 2.0?

Pierstoval commented 8 years ago

As there are some helpers for VichUploaderBundle in EasyAdmin, you might want to take a look at this issue: dustin10/VichUploaderBundle#276 , because it contains exactly what you need.

If you want multiple file uploads, then you should have a *ToMany relationship with an entity that contains medias/images/files, then use the collection form type for your field, and finally add VichFile or VichImage as prototype for this collection form field.

I have not tested it, but it should be working actually, and the issue on the bundle might give some clues about how to do it.

If you test it and find it to be working, it would be great for you to propose a cookbook to add to EasyAdmin ;)

reypm commented 8 years ago

@mkalisz77 I am trying to do the same so if you get it soon and can share your solution sounds good perhaps this could be added to the guides related to Files uploads

mkalisz77 commented 8 years ago

I'll try to setup multiple upload, but this OneupUploaderBundle seems to be more user friendly than VichUploader.

javiereguiluz commented 8 years ago

I'm closing this issue as "won't fix" ... but if someone sets up this multiple upload thing with VichUploader bundle and explains the process to me, I will gladly improve the How to Upload Files and Images tutorial. Thanks!

juaruipi commented 8 years ago

I got this to work with VichUploaderBundle.

I wanted to have an entity to hold images for several other entities and this is what I did:

First of all I created the Image entity (it has a name field that I needed for this case which also works):

AppBundle\Entity\Image

<?php

namespace AppBundle\Entity;

use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\HttpFoundation\File\File;
use Vich\UploaderBundle\Mapping\Annotation as Vich;

/**
 * Image
 *
 * @ORM\Table()
 * @ORM\Entity()
 * @Vich\Uploadable
 */
class Image
{
    /**
     * @var int
     *
     * @ORM\Column(name="id", type="integer")
     * @ORM\Id
     * @ORM\GeneratedValue(strategy="AUTO")
     */
    private $id;

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

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

    /**
     * @var File
     *
     * @Vich\UploadableField(mapping="images", fileNameProperty="image")
     */
    private $imageFile;

    /**
     * @var \DateTime
     *
     * @ORM\Column(type="datetime", length=255)
     */
    private $updatedAt;

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

    /**
     * Set name
     *
     * @param string $name
     *
     * @return Image
     */
    public function setName($name)
    {
        $this->name = $name;

        return $this;
    }

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

    /**
     * @param File|null $image
     * @return Image
     */
    public function setImageFile(File $image = null)
    {
        $this->imageFile = $image;

        if ($image) {
            $this->updatedAt = new \DateTime('now');
        }

        return $this;
    }

    /**
     * @return File
     */
    public function getImageFile()
    {
        return $this->imageFile;
    }

    /**
     * @param string $image
     * @return Image
     */
    public function setImage($image)
    {
        $this->image = $image;

        return $this;
    }

    /**
     * @return string
     */
    public function getImage()
    {
        return $this->image;
    }
}

And its form type:

AppBundle\Form\ImageType

<?php

namespace AppBundle\Form;

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

class ImageType extends AbstractType
{
    /**
     * @param FormBuilderInterface $builder
     * @param array $options
     */
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder
            ->add('name')
            ->add('imageFile', VichFileType::class)
        ;
    }

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

One of the owner entities:

AppBundle\Entity\Owner

<?php

namespace AppBundle\Entity;

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

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

    /**
     * @var Image[]|ArrayCollection
     *
     * @ORM\ManyToMany(targetEntity="Image", cascade={"persist"})
     * @ORM\JoinTable(name="owner_images",
     *      joinColumns={@ORM\JoinColumn(name="owner_id", referencedColumnName="id")},
     *      inverseJoinColumns={@ORM\JoinColumn(name="image_id", referencedColumnName="id", unique=true)}
     * )
     */
    private $images;

    /**
     * Owner constructor.
     */
    public function __construct()
    {
        $this->images = new ArrayCollection();
    }

    /**
     * Get images
     *
     * @return Image[]|ArrayCollection
     */
    public function getImages()
    {
        return $this->images;
    }
}

Then I configured the Vich mapping and easy admin config:

app/config/config.yml

parameters:
    app.path.images: /images/uploaded/

vich_uploader:
    db_driver: orm
    mappings:
        images:
            uri_prefix:         %app.path.images%
            upload_destination: %kernel.root_dir%/../web/%app.path.images%

easy_admin:
    entities:
        Owner:
            class: AppBundle\Entity\Owner
            form:
                fields: [{ property: 'images', type: 'collection', type_options: { entry_type: 'AppBundle\Form\ImageType' }}]
            list:
                fields: ['id','images']

Haven't had time to find a better solution but I hope this could help someone.

mysiar commented 7 years ago

@juaruipi I tried to follow above example to get OneToMany relation with images for rooms: RoomImage that suppose to store images for class Room

<?php

namespace AppBundle\Entity;

use Doctrine\ORM\Mapping as ORM;
use Vich\UploaderBundle\Mapping\Annotation as Vich;
use Symfony\Component\HttpFoundation\File\File;

/**
 * RoomImage
 *
 * @ORM\Table(name=room_images")
 * @ORM\Entity(repositoryClass="AppBundle\Repository\RoomImageRepository")
 * @Vich\Uploadable
 */
class RoomImage
{

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

    /**
     * @var integer
     *
     * @ORM\Column(name="id", type="integer", nullable=false)
     * @ORM\Id
     * @ORM\GeneratedValue(strategy="SEQUENCE")
     * @ORM\SequenceGenerator(sequenceName="room_images_id_seq", allocationSize=1, initialValue=1)
     */
    private $id;

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

    /**
     * @var File
     *
     * @Vich\UploadableField(mapping="room_images", fileNameProperty="image")
     */
    private $imageFile;

    /**
     * @var \DateTime
     *
     * @ORM\Column(type="datetime", length=255)
     */
    private $updatedAt;

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

    /**
     * Set room
     *
     * @param Room $room
     *
     * @return RoomImage
     */
    public function setRoom(Room $room = null)
    {
        $this->room = $room;

        return $this;
    }

    /**
     * Get room
     *
     * @return Room
     */
    public function getRoom()
    {
        return $this->room;
    }

    /**
     * @param File|null $image
     * @return Image
     */
    public function setImageFile(File $image = null)
    {
        $this->imageFile = $image;

        if ($image) {
            $this->updatedAt = new \DateTime('now');
        }

        return $this;
    }

    /**
     * @return File
     */
    public function getImageFile()
    {
        return $this->imageFile;
    }

    /**
     * @param string $image
     * @return Image
     */
    public function setImage($image)
    {
        $this->image = $image;

        return $this;
    }

    /**
     * @return string
     */
    public function getImage()
    {
        return $this->image;
    }

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

class Room

<?php

namespace Agh\GeoEpomBundle\Entity;

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

/**
 * Room
 *
 * @ORM\Table(name="rooms")
 * @ORM\Entity(repositoryClass="AppBundle\Repository\RoomRepository")
 * @Gedmo\Loggable()
 */
class Room
{
    /**
     * @var RoomImage[]|ArrayCollection
     *
     * @ORM\OneToMany(targetEntity="RoomImage", mappedBy="room", cascade={"persist"})
     */
    private $images;

  // other functionality of the class

   /**
     * Get images
     *
     * @return RoomImages[]|ArrayCollection
     */
    public function getImages()
    {
        return $this->images;
    }

    /**
     * Add image
     *
     * @param RoomImage $image
     *
     * @return Room
     */
    public function addImage(RoomImage $image)
    {
        $image->setRoom($this);
        $this->images[] = $image;

        dump($image);

        return $this;
    }

    /**
     * Remove image
     *
     * @param RoomImage $image
     */
    public function removeImage(RoomImage $image)
    {
        $image->setRoom(null);
        $this->images->removeElement($image);
    }

I modified ImageType as I do not need name field

<?php

namespace AppBundle\Form;

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

class ImageType extends AbstractType
{
    /**
     * @param FormBuilderInterface $builder
     * @param array $options
     */
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder
//            ->add('name')
            ->add('imageFile', VichFileType::class);
        dump($builder);
    }

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

config file

parameters:
    app.path.room_images: /uploads/images/rooms

vich_uploader:
    db_driver: orm

    mappings:
        room_images:
            uri_prefix:         %app.path.room_images%
            upload_destination: %kernel.root_dir%/../web/uploads/images/rooms

I have it partially working. I have form in EasyAdmin, can select and add many files but the only info stored in RoomImage table is:

id room_id image updated_at
1 NULL image1.jpg 2017-01-17 13:15:15
2 NULL image2.jpg 2017-01-17 13:25:35
juaruipi commented 7 years ago

@mysiar Have you tried setting by_reference option to false for the images collection field?

easy_admin:
    entities:
        Owner:
            class: AppBundle\Entity\Room
            form:
                fields: [{ property: 'images', type: 'collection', type_options: { entry_type: 'AppBundle\Form\RoomImage', by_reference: false }}]
mysiar commented 7 years ago

@juaruipi THANKS FOR THAT :) working as charm now for adding images but problem is for editing

Error: Maximum function nesting level of '100' reached, aborting!

mysiar commented 7 years ago

@juaruipi I found the problem. To solve it I had to add below config to php.ini

[xdebug]
xdebug.max_nesting_level = 1000
Pierstoval commented 7 years ago

@mysiar the problem is not about the max nesting level, if you have more than 100 nested functions, you may have errors in your templates or something, you should debug that before.

juaruipi commented 7 years ago

@mysiar Have you removed all the calls to the dump function? If it's using var_dump or similar, you can have problems when dumping entities or other objects. In this case, your image instance is now pointing to its room and you've an infinite loop there.

mysiar commented 7 years ago

@javiereguiluz I do not have any dump functions running @Pierstoval I have entity with 7 relations so maybe this was a problem

mysiar commented 7 years ago

Is there any reason why below config line cause problem ?

- { property: 'image', type: 'image', base_path: %vich_uploader.mappings.room_image% }

my vich configuration is:

parameters:
    app.path.room_images: /uploads/images/rooms

vich_uploader:
    db_driver: orm

    mappings:
        room_image:
            uri_prefix:         %app.path.room_images%
            upload_destination: %kernel.root_dir%/../web/uploads/images/rooms
            namer:              vich_uploader.namer_origname
            directory_namer:
                service: vich_uploader.directory_namer_subdir
                options: {chars_per_dir: 4}

error message says

ParameterNotFoundException in ParameterBag.php line 100: 
You have requested a non-existent parameter "vich_uploader.mappings.room_image".
mysiar commented 7 years ago

@juaruipi I have another implementation of you above code for ManyToMany this time.

Class Room

class Room
{
 /**
     * @var RoomDocument[]|ArrayCollection
     *
     * @ORM\ManyToMany(targetEntity="RoomDocument", cascade={"persist"})
     * @ORM\JoinTable(name="_room_documents",
     *      joinColumns={@ORM\JoinColumn(name="room_id", referencedColumnName="id")},
     *      inverseJoinColumns={@ORM\JoinColumn(name="document_id", referencedColumnName="id")}
     * )
     *
     */
    private $documents;

   /**
     * Add document
     *
     * @param \AppBundle\Entity\RoomDocument $document
     *
     * @return Room
     */
    public function addDocument(\AppBundle\Entity\RoomDocument $doc)
    {
        $this->documents[] = $doc;
        $doc->addRoom($this);

        return $this;
    }

    /**
     * Remove document
     *
     * @param \AppBundle\Entity\RoomDocument $doc
     */
    public function removeDocument(\AppBundle\Entity\RoomDocument $doc)
    {
        $this->documents->removeElement($doc);
        $doc->removeRoom($this);
    }

    /**
     * Get documents
     *
     * @return RoomDocument[]|ArrayCollection
     */
    public function getDocuments()
    {
        return $this->documents;
    }
}

Class RoomDocument

class RoomDocument
{

    /**
     * @ORM\ManyToMany(targetEntity="Room", mappedBy="documents")
     */
    private $rooms;

    /**
     * @var integer
     *
     * @ORM\Column(name="id", type="integer", nullable=false)
     * @ORM\Id
     * @ORM\GeneratedValue(strategy="SEQUENCE")
     * @ORM\SequenceGenerator(sequenceName="geoepom.room_documents_id_seq", allocationSize=1, initialValue=1)
     */
    private $id;

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

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

    /**
     * @var File
     *
     * @Vich\UploadableField(mapping="room_doc", fileNameProperty="document")
     */
    private $documentFile;

    /**
     * @var \DateTime
     *
     * @ORM\Column(name="updated_at", type="datetime", length=255)
     */
    private $updatedAt;

    /**
     * RoomDocument constructor.
     *
     */
    public function __construct()
    {
        $this->rooms = new \Doctrine\Common\Collections\ArrayCollection();
    }

    public function __toString()
    {
        return (string)$this->document;
    }

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

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

    /**
     * @return string
     */
    public function getDocument()
    {
        return $this->document;
    }

    /**
     * @return File
     */
    public function getDocumentFile()
    {
        return $this->documentFile;
    }

    /**
     * @return \DateTime
     */
    public function getUpdatedAt()
    {
        return $this->updatedAt;
    }

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

    /**
     * @param string $document
     * @return RoomDocument
     */
    public function setDocument($document)
    {
        $this->document = $document;
        return $this;
    }

    /**
     * @param File $documentFile
     * @return RoomDocument
     */
    public function setDocumentFile(File $documentFile = null)
    {
        $this->documentFile = $documentFile;
        if ($documentFile) {
            $this->updatedAt = new \DateTime('now');
        }
        return $this;
    }

    /**
     * Add room
     *
     * @param \AppBundle\Entity\Room $room
     *
     * @return RoomDocument
     */
    public function addRoom(\AppBundle\Entity\Room $room)
    {
        $this->rooms[] = $room;

        return $this;
    }

    /**
     * Remove room
     *
     * @param \AppBundle\Entity\Room $room
     */
    public function removeRoom(\AppBundle\Entity\Room $room)
    {
        $this->rooms->removeElement($room);
    }

    /**
     * Get rooms
     *
     * @return \Doctrine\Common\Collections\ArrayCollection
     */
    public function getRooms()
    {
        return $this->rooms;
    }
}

The problem I'm facing now is that I can't have it write on inverse side despite updating room on document side and using easy admin config

- { property: 'documents', label: label.documents, type: 'collection', type_options: { entry_type: 'AppBundle\Form\RoomDocumentType', by_reference: false }}
quentin-th commented 7 years ago

Hello,

I tried @juaruipi's solution because I need to have an entity to hold images for other entities too but I have this error:

Could not load type "HeliMusicBundle\Form\ImageType"

I tried cache:clear but still the same error. I checked my use but it seems fine.

Can you help me with this ?

Here's my code:

HeliMusicBundle\Entity\Image

<?php

namespace Projet\HeliMusicBundle\Entity;

use Doctrine\ORM\Mapping as ORM;
use Doctrine\Common\Collections\ArrayCollection;
use Symfony\Component\HttpFoundation\File\File;
use Vich\UploaderBundle\Mapping\Annotation as Vich;

/**
 * Image
 *
 * @ORM\Table(name="image")
 * @ORM\Entity(repositoryClass="Projet\HeliMusicBundle\Repository\ImageRepository")
 * @Vich\Uploadable
 */
class Image
{
    /**
     * @var int
     *
     * @ORM\Column(name="id", type="integer")
     * @ORM\Id
     * @ORM\GeneratedValue(strategy="AUTO")
     */
    private $id;

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

    /**
     * @var string
     */
    private $image;

    /**
     * @var File
     *
     * @Vich\UploadableField(mapping="cover_img", fileNameProperty="image")
     */
    private $imageFile;

    /**
     * @var int
     *
     * @ORM\Column(name="img_size", type="integer", nullable=true)
     */
    private $imgSize;

    /**
     * @var \DateTime
     *
     * @ORM\Column(name="created_at", type="datetime")
     */
    private $createdAt;

    /**
     * @var \DateTime
     *
     * @ORM\Column(name="updated_at", type="datetime")
     */
    private $updatedAt;

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

    /**
     * Set name
     *
     * @param string $name
     *
     * @return Image
     */
    public function setName($name)
    {
        $this->name = $name;

        return $this;
    }

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

    /**
     * @param File|null $image
     * @return Image
     */
    public function setImageFile(File $image = null)
    {
        $this->imageFile = $image;

        if ($image) {
            $this->updatedAt = new \DateTime('now');
        }

        return $this;
    }

    /**
     * @return File
     */
    public function getImageFile()
    {
        return $this->imageFile;
    }

    /**
     * @param string $image
     * @return Image
     */
    public function setImage($image)
    {
        $this->image = $image;

        return $this;
    }

    /**
     * @return string
     */
    public function getImage()
    {
        return $this->image;
    }

    /**
     * Set imgSize
     *
     * @param integer $imgSize
     *
     * @return Image
     */
    public function setImgSize($imgSize)
    {
        $this->imgSize = $imgSize;

        return $this;
    }

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

    /**
     * Set createdAt
     *
     * @param \DateTime $createdAt
     *
     * @return Image
     */
    public function setCreatedAt($createdAt)
    {
        $this->createdAt = $createdAt;

        return $this;
    }

    /**
     * Get createdAt
     *
     * @return \DateTime
     */
    public function getCreatedAt()
    {
        return $this->createdAt;
    }

    /**
     * Set updatedAt
     *
     * @param \DateTime $updatedAt
     *
     * @return Image
     */
    public function setUpdatedAt($updatedAt)
    {
        $this->updatedAt = $updatedAt;

        return $this;
    }

    /**
     * Get updatedAt
     *
     * @return \DateTime
     */
    public function getUpdatedAt()
    {
        return $this->updatedAt;
}

HeliMusicBundle\Form\ImageType

<?php

namespace HeliMusicBundle\Form;

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

class ImageType extends AbstractType
{
    /**
     * @param FormBuilderInterface $builder
     * @param array $options
     */
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder
            ->add('name')
            ->add('imageFile', VichFileType::class)
        ;
    }

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

HeliMusicBundle\Entity\Image

<?php

namespace Projet\HeliMusicBundle\Entity;

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

/**
 * Page
 *
 * @ORM\Table(name="page")
 * @ORM\Entity(repositoryClass="Projet\HeliMusicBundle\Repository\PageRepository")
 */
class Page
{
    /**
     * @var int
     *
     * @ORM\Column(name="id", type="integer")
     * @ORM\Id
     * @ORM\GeneratedValue(strategy="AUTO")
     */
    private $id;

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

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

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

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

    /**
     * @var string
     *
     * @ORM\Column(name="meta_author", type="string", length=50, nullable=true)
     */
    private $metaAuthor;

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

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

    /**
     * @var Image[]|ArrayCollection
     *
     * @ORM\ManyToMany(targetEntity="Image", cascade={"persist"})
     * @ORM\JoinTable(name="page_images",
     *      joinColumns={@ORM\JoinColumn(name="page_id", referencedColumnName="id")},
     *      inverseJoinColumns={@ORM\JoinColumn(name="image_id", referencedColumnName="id", unique=true)}
     * )
     */
    private $images;

    /**
     * @var \DateTime
     *
     * @ORM\Column(name="created_at", type="datetime")
     */
    private $createdAt;

    /**
     * @var \DateTime
     *
     * @ORM\Column(name="updated_at", type="datetime")
     */
    private $updatedAt;

    /**
     * @ORM\ManyToMany(targetEntity="Projet\HeliMusicBundle\Entity\Video", cascade={"persist"})
     */
    private $videos;

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

    /**
     * Set metaTitle
     *
     * @param string $metaTitle
     *
     * @return Page
     */
    public function setMetaTitle($metaTitle)
    {
        $this->metaTitle = $metaTitle;

        return $this;
    }

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

    /**
     * Set metaKeywords
     *
     * @param string $metaKeywords
     *
     * @return Page
     */
    public function setMetaKeywords($metaKeywords)
    {
        $this->metaKeywords = $metaKeywords;

        return $this;
    }

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

    /**
     * Set metaContent
     *
     * @param string $metaContent
     *
     * @return Page
     */
    public function setMetaContent($metaContent)
    {
        $this->metaContent = $metaContent;

        return $this;
    }

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

    /**
     * Set metaAuthor
     *
     * @param string $metaAuthor
     *
     * @return Page
     */
    public function setMetaAuthor($metaAuthor)
    {
        $this->metaAuthor = $metaAuthor;

        return $this;
    }

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

    /**
     * Set pageTitle
     *
     * @param string $pageTitle
     *
     * @return Page
     */
    public function setPageTitle($pageTitle)
    {
        $this->pageTitle = $pageTitle;

        return $this;
    }

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

    /**
     * Set footerTitle
     *
     * @param string $footerTitle
     *
     * @return Page
     */
    public function setFooterTitle($footerTitle)
    {
        $this->footerTitle = $footerTitle;

        return $this;
    }

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

    /**
     * Set createdAt
     *
     * @param \DateTime $createdAt
     *
     * @return Page
     */
    public function setCreatedAt($createdAt)
    {
        $this->createdAt = $createdAt;

        return $this;
    }

    /**
     * Get createdAt
     *
     * @return \DateTime
     */
    public function getCreatedAt()
    {
        return $this->createdAt;
    }

    /**
     * Set updatedAt
     *
     * @param \DateTime $updatedAt
     *
     * @return Page
     */
    public function setUpdatedAt($updatedAt)
    {
        $this->updatedAt = $updatedAt;

        return $this;
    }

    /**
     * Get updatedAt
     *
     * @return \DateTime
     */
    public function getUpdatedAt()
    {
        return $this->updatedAt;
    }

    /**
     * page constructor.
     */
    public function __construct()
    {
        $this->images = new ArrayCollection();

        $this->albums = new \Doctrine\Common\Collections\ArrayCollection();
        $this->posts = new \Doctrine\Common\Collections\ArrayCollection();
        $this->videos = new \Doctrine\Common\Collections\ArrayCollection();
    }

    /**
     * Add album
     *
     * @param \Projet\HeliMusicBundle\Entity\Album $album
     *
     * @return Page
     */
    public function addAlbum(\Projet\HeliMusicBundle\Entity\Album $album)
    {
        $this->albums[] = $album;

        return $this;
    }

    /**
     * Remove album
     *
     * @param \Projet\HeliMusicBundle\Entity\Album $album
     */
    public function removeAlbum(\Projet\HeliMusicBundle\Entity\Album $album)
    {
        $this->albums->removeElement($album);
    }

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

    /**
     * Get images
     *
     * @return Image[]|ArrayCollection
     */
    public function getImages()
    {
        return $this->images;
    }

    /**
     * Add post
     *
     * @param \Projet\HeliMusicBundle\Entity\Post $post
     *
     * @return Page
     */
    public function addPost(\Projet\HeliMusicBundle\Entity\Post $post)
    {
        $this->posts[] = $post;

        return $this;
    }

    /**
     * Remove post
     *
     * @param \Projet\HeliMusicBundle\Entity\Post $post
     */
    public function removePost(\Projet\HeliMusicBundle\Entity\Post $post)
    {
        $this->posts->removeElement($post);
    }

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

    /**
     * Add video
     *
     * @param \Projet\HeliMusicBundle\Entity\Video $video
     *
     * @return Page
     */
    public function addVideo(\Projet\HeliMusicBundle\Entity\Video $video)
    {
        $this->videos[] = $video;

        return $this;
    }

    /**
     * Remove video
     *
     * @param \Projet\HeliMusicBundle\Entity\Video $video
     */
    public function removeVideo(\Projet\HeliMusicBundle\Entity\Video $video)
    {
        $this->videos->removeElement($video);
    }

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

    /**
     * Set name
     *
     * @param string $name
     *
     * @return Page
     */
    public function setName($name)
    {
        $this->name = $name;

        return $this;
    }

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

And config files

#Vich config

vich_uploader:
    db_driver: orm

    mappings:
        cover_img:
            uri_prefix:         /web/uploads/cover
            upload_destination: '%kernel.root_dir%/../web/uploads/cover'
            namer:
                service: vich_uploader.namer_origname

#easy_admin config
easy_admin:
entities:
        Page:
            class: Projet\HeliMusicBundle\Entity\Page
            label: 'Pages'
            list:
                title: 'Listing des %%entity_label%%'
                fields:
                    - 'id'
                    - 'cover_img'
                    - { property: 'name', label: 'Nom de la page'}
                    - { property: 'createdAt', label: 'Date de création' }
                    - { property: 'updatedAt', label: 'Date de mise à jour' }
            form:
                title: 'Edition'
                fields:
                    - { property: 'name', label: 'Nom de la page' }
                    - { property: 'metaTitle', help: 'Titre apparaîssant dans l''onglet' }
                    - { property: 'metaKeywords', help: 'SEO - Mots clé de la page (non apparants sur le site)' }
                    - { property: 'metaContent', help: 'SEO - Contenu général de la page (non apparant sur le site)' }
                    - { property: 'metaAuthor', help: 'SEO - Auteur de la page (non apparant sur le site)' }
                    - { property: 'pageTitle', label: 'Titre de la page', help: 'Titre apparaîssant sur la photo d''en-tête' }
                    - { property: 'images', type: 'collection', type_options: { entry_type: 'HeliMusicBundle\Form\ImageType' }}

Thanks !

juaruipi commented 7 years ago

Hi @Tinougit,

Maybe you had an error when pasting your code, but just to be sure, are you missing the php opening tag in your ImageType.php file?

<?php

namespace HeliMusicBundle\Form;
quentin-th commented 7 years ago

Hi,

I'm sorry, I forgot to copy the php tag, but it's in my ImageType.php file. (code edited here)

juaruipi commented 7 years ago

@Tinougit are you using Symfony 2.7?

If so, I think you'll need to define your form as a service and use its id instead.

quentin-th commented 7 years ago

@juaruipi

Thanks for reply.

I'm using Symfony 3.3.6, you were on Symfony 3.x when you posted your solution ?

juaruipi commented 7 years ago

@Tinougit I see you use the prefix "Projet" in all the other namespaces. Are you missing this part in your ImageType's namespace declaration?

quentin-th commented 7 years ago

@juaruipi yes it was missing in ImageType namespace, I added it, clear cache, but still the same error :(

juaruipi commented 7 years ago

@Tinougit did you change it in both the file and the config (entry_type)?

If yes, your best option would be to fork symfony-standard and commit the minimum setup to reproduce your issue.

quentin-th commented 7 years ago

@juaruipi well done, it's working !! I forget the prefix in the config (and 'data_class' too)...

Thanks a lot ! :)

dsdeiz commented 7 years ago

Anyone happen to know what might be the cause when the "Delete" checkbox when ticked doesn't delete the entity nor unset the referenced entity. Referring to this checkbox.

juaruipi commented 7 years ago

@dsdeiz That field is part of the VichFileType and it is intended to delete the reference to the file but not the entity instance itself. This means setting the entity property which holds the path to the uploaded file (i.e. imageFile) to null. For this to work, the property must be nullable or you'll get a doctrine exception when using the checkbox.

If you don't want this field to appear, you should set the VichFileType option 'allow_delete' to false:

->add('imageFile', VichFileType::class [ 'allow_delete' => false ])

To delete an item from the collection, use the "Remove the item" link instead: http://imgur.com/meGh9I7

Anna5555 commented 7 years ago

Hello. Thank you for sharing. Beginner on Symfony, having followed your advice with the help of your codes, I have an error when sending an image:

NoSuchPropertyException HTTP 500 Internal Server Error Could not determine access type for property "images" in class "MO\WebAppBundle\Entity\WebApp".

Here is my code :

My form :


{{ form_start(form, {'attr': {'class': 'form-horizontal'}}) }}
...
{{ form_row(form.images) }}
...

{{ form_end(form) }}

> WebApp.php = parent

<?php

namespace MO\WebAppBundle\Entity;

use Doctrine\ORM\Mapping as ORM; use Doctrine\Common\Collections\ArrayCollection; //pour le slug use Gedmo\Mapping\Annotation as Gedmo;

/**


> Image.php = multiples children

<?php

namespace MO\WebAppBundle\Entity;

use Doctrine\ORM\Mapping as ORM; use Symfony\Component\HttpFoundation\File\File; use Vich\UploaderBundle\Mapping\Annotation as Vich;

/**


> WebAppType.php

<?php

namespace MO\WebAppBundle\Form;

use MO\WebAppBundle\Entity\Image; use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\Extension\Core\Type\CheckboxType; use Symfony\Component\Form\Extension\Core\Type\DateTimeType; use Symfony\Component\Form\Extension\Core\Type\CollectionType; use Symfony\Bridge\Doctrine\Form\Type\EntityType; use Symfony\Component\Form\Extension\Core\Type\SubmitType; use Symfony\Component\Form\Extension\Core\Type\TextareaType; use Symfony\Component\Form\Extension\Core\Type\TextType; use Symfony\Component\Form\Extension\Core\Type\FileType; use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\OptionsResolver\OptionsResolver; use Symfony\Component\Form\FormEvent; use Symfony\Component\Form\FormEvents;

class WebAppType extends AbstractType { /**

ImageType.php

<?php

namespace MO\WebAppBundle\Form;

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

class ImageType extends AbstractType
{
    /**
     * @param FormBuilderInterface $builder
     * @param array $options
     */
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder
            ->add('name')
            ->add('images', VichFileType::class);
    }

    /**
     * {@inheritdoc}
     */
    public function configureOptions(OptionsResolver $resolver)
    {
        $resolver->setDefaults(array(
            'data_class' => 'MO\WebAppBundle\Entity\Image'
        ));
    }

}

config.yml


# VichUploaderBundle
vich_uploader:
    db_driver: orm
    mappings:
        image_webapp:
            uri_prefix:         /uploads/img
            upload_destination: '%kernel.root_dir%/../web/uploads/img'

            # le fichier doit être supprimé lorsqu'un nouveau fichier est téléchargé
            delete_on_update :    true
            #le fichier doit être supprimé lorsque l'entité est supprimée
            delete_on_remove :    true

easy_admin:
    entities:
        WebApp:
            class: MO\WebAppBundle\Entity\WebApp
            form:
                fields: [{ property: 'images', type: 'collection', type_options: { entry_type: 'MO\WebAppBundle\Form\ImageType' }}]
            list:
                fields: ['id','image_webapp']
maximaam commented 6 years ago

@Anna5555 did you fix that? I have the same issue and cannot get out of it. thx

Arielblues commented 5 years ago

I just used the above examples to make this viche multiupload work. This multiupload works by "adding another item".

Are there any new improvemts in this field? By actually selecting whole bunch of images at the same time? And even better also have a drag and drop reorder once files are uploaded?

Holicz commented 5 years ago

@Anna5555 you are missing functions addImage() and removeImage() in WebApp class.

utheron commented 5 years ago

@juaruipi 3 years later, your solution's still relevant. Can't thank you enough o/

Still wondering if this can be improved like @Arielblues suggestions.

byhoratiss commented 4 years ago

Thank you @juaruipi for this!

paulalbertdev commented 3 years ago

If someone wants to use an <input type="file" multiple> for adding a bunch of images @ the same time in Easy Admin 3, I've found a pretty straightforward solution, here it is :

namespace App\Field;

use EasyCorp\Bundle\EasyAdminBundle\Contracts\Field\FieldInterface; use EasyCorp\Bundle\EasyAdminBundle\Field\FieldTrait; use Symfony\Component\Form\Extension\Core\Type\FileType;

final class MultipleImagesField implements FieldInterface { use FieldTrait;

public static function new(string $propertyName, ?string $label = null): self
{
    return (new self())
        ->setProperty($propertyName)
        ->setLabel($label)

        // this is used in 'edit' and 'new' pages to edit the field contents
        // you can use your own form types too
        ->setFormType(FileType::class)
        ->setFormTypeOptions(['multiple' => true, 'data_class' => null])
        ->addCssClass('field-multiple-images');
}

}


- Then just add those methods correspondig to the "virtual multipleImages property" (so don't create the property, only the getter/setter).

public function getMultipleImages() { return null; }

public function setMultipleImages(array $imagesUploadedFiles) // in fact it's more like an "add" than a "set" { foreach ($imagesUploadedFiles as $uploadedFile) { $i = new CreationImage(); $i->setImageFile($uploadedFile); $this->addImage($i); } }


This way your already uploaded files list will be followed by an input tag with the multiple prop and you'll be able to upload a lot of files at the same time !
paulalbertdev commented 3 years ago

Go there to see the next step : adding drag & drop https://github.com/EasyCorp/EasyAdminBundle/issues/2662#issuecomment-832978054