dustin10 / VichUploaderBundle

A simple Symfony bundle to ease file uploads with ORM entities and ODM documents.
MIT License
1.84k stars 519 forks source link

Upload multiple files with Symfony 5.3.3, EasyAdmin 3.3 and vichUploaderBundle 1.18 #1216

Closed cabedux closed 3 years ago

cabedux commented 3 years ago

We are working in a project where we need upload several files when we created and edit an entity with EasyAdmin 3. We use vichupload Bundle to upload the files. We have tried two ways to upload files.

Firts, we have created the next two entities Expedient and Attachment.

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

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

    /**
     *@ORM\OneToMany(targetEntity="Attachment", mappedBy="expedient", cascade={"persist"})
     */
    private $attachments;

 public function getAttachments(): ?Collection
    {
        return $this->attachments;
    }

    public function addAttachment(?UploadedFile $attachment): self
    {
            $attachmentObject = new Attachment();
            $attachmentObject->setFile($attachment);
            $attachmentObject->setExpedient($this);
            $this->attachments[] = $attachementObject;

        return $this;
    }

    public function removeAttachment(Attachment $attachment): self
    {
        if ($this->attachments->removeElement($attachment)) {
            if ($attachment->getExpedient() === $this) {
                $attachment->setExpedient(null);
            }
        }

        return $this;
    }`
`/**
 * @ORM\Entity
 * @Vich\Uploadable
 */
class Attachment
{
    /**
     * @ORM\Id
     * @ORM\GeneratedValue
     * @ORM\Column(type="integer")
     */
    private $id;

    /**
     * @ORM\Column(type="string", nullable=true)
     */
    private $fileName;

    /**
     * @Vich\UploadableField(mapping="model", fileNameProperty="fileName")
     */
    private $file;

    /**
     * @ORM\Column(type="datetime", nullable=true)
     */
    private $fileUpdatedAt;

    /**
     *@ORM\ManyToOne(targetEntity="Expedient", inversedBy="attachments")
     */
    private $expedient;

    public function __construct()
    {
        $this->fileUpdatedAt = new \DateTime();
    }

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

    public function setId(int $id): self
    {
        $this->id = $id;

        return $this;
    }

   public function setFileName(?string $fileName): self
    {
        $this->fileName = $fileName;

        return $this;
    }

    public function getFileName(): ?string
    {
        return $this->fileName;
    }

    public function setFile(?File $file = null): self
    {
        $this->file = $file;

        if (null !== $file) {
            $this->fileUpdatedAt = new \DateTime();
        }

        return $this;
    }

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

    public function getFileUpdatedAt(): ?\DateTime
    {
        return $this->fileUpdatedAt;
    }

    public function setFileUpdatedAt(?\DateTime $fileUpdatedAt): self
    {
        $this->fileUpdatedAt = $fileUpdatedAt;

        return $this;
    }

    public function getExpedient()
    {
        return $this->expedient;
    }

    public function setExpedient($expedient): void
    {
        $this->expedient = $expedient;
    }
}`

At the first way, we include this code in ExpedientCrudController.php. For this way, we can upload several files when created a new entity Expedient. But when we want edit entity Expedient, symfony throw an exception =>NotUploaddableException

The class "Doctrine\ORM\PersistentCollection" is not uploadable. If you use annotations to configure VichUploaderBundle, you probably just forgot to add @Vich\Uploadable on top of your entity. If you don't use annotations, check that the configuration files are in the right place. In both cases, clearing the cache can also solve the issue.

public function configureFields(string $pageName): iterable
  {
  ...
 $fields[] = CollectionField::new('attachments', $this->translator->trans('pages.rule.common.image_file'))
                    ->setEntryType(VichFileType::class);

  ...
  }

The second way, We have both entities too, Expedient and Attachment. We have created a custom field (CollectionFileField) and have add in configure fields to field attachments in Expedient entity.

  ExpedientCrudContoller.php

  public function configureFields(string $pageName): iterable
  {
  ...
$fields[] = CollectionFileField::new('attachments', $this->translator->trans('pages.rule.common.image_file'));

  ...
  }

CollectionFileField have a custom Form type (CollectionFileType ) . CollectionFileType is a Collection of VichFileType.

  class CollectionFileField implements FieldInterface
{
    use FieldTrait;

    public static function new(string $propertyName, ?string $label = null)
    {
        return (new self())
            ->setProperty($propertyName)
            ->setTemplatePath('')
            ->setLabel($label)
            ->addCssClass('field-collection-file')
            ->setFormType(CollectionFileType::class)
            ->setFormTypeOption('block_name' , 'custom_collection_file')
            ;
    }
}
class CollectionFileType extends AbstractType

{

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

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

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

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

By this way, we rewrite template to use to upload several files.

public function configureCrud(Crud $crud): Crud
    {
        return $crud
           ->setFormThemes(['admin/form.html.twig', '@EasyAdmin/crud/form_theme.html.twig'])
        ;
    }
admin/form.html.twig (template)
---------------------
    <ul id="attachment-fields-list"
    data-prototype="{{ form_widget(form.attachments.vars.prototype)|e }}"
    data-widget-tags="{{ '<li></li>'|e }}"
    data-widget-counter="{{ form.attachments|length }}">
        {% for field in form.attachments %}
                <li>
                        {{ form_errors(field) }}
                        {{ form_widget(field) }}
                </li>
        {% endfor %}
</ul>

<button type="button"
        class="add-another-collection"
        data-list-selector="#attachment-fields-list">Add another 
</button>
uploadFiles.js (JS to add new input file)
--------------
jQuery(document).ready(function () {
    jQuery('.add-another-collection').click(function (e) {
        var list = $("#attachment-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();

        });
    });
});

For this way, when we want to save a new/edit entity, the validator of form show an error : This value is not valid.

In resumen, with the first way, we can create new expedient with attachments, but we can´t edit entity. For the second way, we can´t create or edit.

Any idea on this problem?

garak commented 3 years ago

Ask EasyAdminBundle

cabedux commented 3 years ago

Thanks @garak