Symfony 5. EasyAdmin 3. VichUploader。同时上传多个文件

Posted

技术标签:

【中文标题】Symfony 5. EasyAdmin 3. VichUploader。同时上传多个文件【英文标题】:Symfony 5. EasyAdmin 3. VichUploader. Upload multiple files at the same time 【发布时间】:2021-10-19 08:37:23 【问题描述】:

我有一个完美的多文件上传。使用一个“浏览”按钮一次上传一个文件。它基本上是一个可以包含许多图像的 Places 实体。

我正在尝试将其修改为仅通过一个“浏览”窗口一次上传所有文件。使用 Ctrl / shift 选择多个文件。

所以我得到的第一个内部信息是 VichUploader (VichFileType::class) 不支持多次上传,所以到目前为止我发现只有一个选择是在我的 AttachmentType.php 中将 VichFileType::class 更改为 FileType::class 并添加在选项['multiple' => true] 所以现在我在我的管理面板字段中可以一次选择多个文件。这正是我需要的。但是在我选择了所有需要的文件并单击Create 创建一个新位置后,我得到了错误:Return value of Vich\UploaderBundle\Mapping\PropertyMapping::getFile() must be an instance of Symfony\Component\HttpFoundation\File\File or null, array returned。似乎 VichUploader 只等待一个文件而不是数组,所以我修改了我的图像实体。

之前:

   /**
    * @param mixed $imageFile
    */
   public function setImageFile($imageFile): void 
       $this->imageFile = $imageFile;

       if ($imageFile) 
           $this->updatedAt = new \DateTime();
       
   

之后:

   /**
     * @param mixed $imageFile
     */
    public function setImageFile($imageFile): void 
        foreach ($imageFile as $file) 
            $this->imageFile = $file;
            if ($imageFile) 
                $this->updatedAt = new \DateTime();
            
        
    

之后,错误消失了,但问题是如果我添加多张图片,则只添加数组中的最后一张。

完整代码: Places.php

   /**
     * @ORM\OneToMany(targetEntity=Images::class, mappedBy="place", cascade="persist", "remove")
     */
    private $images;
    
     public function __construct()
    
        $this->images = new ArrayCollection();
    
    
     /**
     * @return Collection|Images[]
     */
    public function getImages(): Collection
    
        return $this->images;
    

    public function addImage(Images $image): self
    
        if (!$this->images->contains($image)) 
            $this->images[] = $image;
            $image->setPlace($this);
        

        return $this;
    

    public function removeImage(Images $image): self
    
        if ($this->images->removeElement($image)) 
            // set the owning side to null (unless already changed)
            if ($image->getPlace() === $this) 
                $image->setPlace(null);
            
        

        return $this;
    

Images.php

   /**
 * @ORM\Entity(repositoryClass=ImagesRepository::class)
 * @Vich\Uploadable()
 */
class Images

    /**
     * @ORM\Id
     * @ORM\GeneratedValue
     * @ORM\Column(type="integer")
     */
    private $id;

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

    /**
     * @Vich\UploadableField(mapping="attachments", fileNameProperty="title")
     */
    private $imageFile;

    /**
     * @ORM\Column(type="datetime")
     */
    private $updatedAt;

    /**
     * @ORM\ManyToOne(targetEntity=Places::class, inversedBy="images")
     */
    private $place;

    /**
     * @ORM\ManyToOne(targetEntity=Regions::class, inversedBy="image")
     */
    private $region;




    public function getTitle(): ?string
    
        return $this->title;
    

    public function setTitle(?string $title): self
    
        $this->title = $title;

        return $this;
    

    public function setUpdatedAt(\DateTimeInterface $updatedAt): self
    
        $this->updatedAt = $updatedAt;

        return $this;
    

    /**
     * @return mixed
     */
    public function getUpdatedAt() 
        return $this->updatedAt;
    

    /**
     * @param mixed $imageFile
     */
    public function setImageFile($imageFile): void 
        foreach ($imageFile as $file) 
            $this->imageFile = $file;
            if ($imageFile) 
                $this->updatedAt = new \DateTime();
            
        
    

    /**
     * @return mixed
     */
    public function getImageFile() 
        return $this->imageFile;
    

    public function getPlace(): ?Places
    
        return $this->place;
    

    public function setPlace(?Places $place): self
    
        $this->place = $place;

        return $this;
    

    public function getRegion(): ?Regions
    
        return $this->region;
    

    public function setRegion(?Regions $region): self
    
        $this->region = $region;

        return $this;
    

    

AttachmentType.php

class AttachmentType extends AbstractType

    public function buildForm(FormBuilderInterface $builder, array $options)
    
        $builder
            ->add('imageFile', FileType::class, [
                'multiple' => true
            ])
            ->add('updatedAt')
            ->add('place')
        ;
    

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

【问题讨论】:

嗨,@laneboyandrew!我建议你开始赏金 50 声望点。它可能会吸引知识渊博的用户。 【参考方案1】:

通过创建我自己的 Field 类,我能够使用 EasyAdmin3 为画廊上传多张图片。

这是我如何实现这一目标的示例......

<?php

namespace App\Controller\Admin\Fields;

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

class MultipleImageField implements FieldInterface

    use FieldTrait;

    public static function new(string $propertyName, ?string $label = null): self
    
        return (new self())
            ->setProperty($propertyName)
            ->setFormType(FileType::class)
            ->setFormTypeOptions([
                'multiple' => true,
                'data_class' => null,
            ]);
    

在我的 Gallery 实体中,我有 2 个字段来处理 imageFile 和 image(只存储文件名)和一个 upload() 函数来处理上传本身。

    const UPLOAD_IMAGE_DIRECTORY = 'uploads/images';

    /**
     * @ORM\Column(type="string", length=255)
     * @Assert\Regex(pattern="/[\/.](gif|jpg|jpeg|tiff|png)$/i",  message="Please upload a valid image")
     */
    private string $image;


    /**
     * Unmapped property to handle file uploads
     */
    private $imageFile;
    
    public function getImage(): ?string
    
        return $this->image;
    

    public function setImage(string $image): self
    
        $this->image = $image;

        return $this;
    

    /**
     * @return mixed
     */
    public function getImageFile()
    
        return $this->imageFile;
    

    /**
     * @param mixed $imageFile
     */
    public function setImageFile($imageFile): void
    
        $this->imageFile = $imageFile;
    

    public function upload($file)
    
        if(null === $file)
            return;
        

        $file->move(
            self::UPLOAD_IMAGE_DIRECTORY,
            $file->getClientOriginalName()
        );

        $this->setImage($file->getClientOriginalName());

        $this->setImageFile(null);
    

在此处断言文件类型可能会做得更好..

然后我将这两个字段添加到我的 EasyAdmin CRUDController,一个用于在索引/详细信息上显示实际图像,一个用于表单,以及 updateEntitypersistEntity 函数。

<?php

namespace App\Controller\Admin;

use App\Controller\Admin\Fields\MultipleImageField;
use App\Entity\Gallery;
use Doctrine\ORM\EntityManagerInterface;
use EasyCorp\Bundle\EasyAdminBundle\Config\Action;
use EasyCorp\Bundle\EasyAdminBundle\Config\Actions;
use EasyCorp\Bundle\EasyAdminBundle\Config\Crud;
use EasyCorp\Bundle\EasyAdminBundle\Config\Filters;
use EasyCorp\Bundle\EasyAdminBundle\Controller\AbstractCrudController;
use EasyCorp\Bundle\EasyAdminBundle\Field\AssociationField;
use EasyCorp\Bundle\EasyAdminBundle\Field\ImageField;

class GalleryCrudController extends AbstractCrudController

    public static function getEntityFqcn(): string
    
        return Gallery::class;
    

    public function configureFilters(Filters $filters): Filters
    
        return $filters
            ->add('category')
        ;
    

    public function configureActions(Actions $actions): Actions
    
        return $actions
            ->add(Crud::PAGE_INDEX, Action::DETAIL)
            ->add(Crud::PAGE_EDIT, Action::SAVE_AND_ADD_ANOTHER)
            ;
    

    public function configureFields(string $pageName): iterable
    
        return [
            AssociationField::new('category'),
            ImageField::new('image')
                ->setUploadDir('public/uploads/images')
                ->setBasePath('uploads/images')
                ->setRequired(false)
                ->hideOnForm(),
            MultipleImageField::new('imageFile')
                ->setRequired(false)
                ->onlyOnForms(),
        ];
    

    /**
     * @param EntityManagerInterface $entityManager
     * @param Gallery $entityInstance
     */
    public function persistEntity(EntityManagerInterface $entityManager, $entityInstance): void
    
        foreach($entityInstance->getImageFile() as $file)
        
            $gallery = (new Gallery())
                ->setCategory($entityInstance->getCategory())
                ->setImage($file->getClientOriginalName())
            ;
            $entityInstance->upload($file);
            $entityManager->persist($gallery);
        
        $entityManager->flush();
    

    /**
     * @param EntityManagerInterface $entityManager
     * @param Gallery $entityInstance
     */
    public function updateEntity(EntityManagerInterface $entityManager, $entityInstance): void
    
        foreach($entityInstance->getImageFile() as $file)
        
            $gallery = (new Gallery())
                ->setCategory($entityInstance->getCategory())
                ->setImage($file->getClientOriginalName())
            ;
            $entityInstance->upload($file);
            $entityManager->persist($gallery);
        
        $entityManager->flush();
    


我希望这对尝试使用 EasyAdmin3 做类似事情的人有所帮助

【讨论】:

感谢您的回答。一段时间后,我回到了这个问题。我的问题是我需要在PlacesCrudController 中保存多个图像,而我的Places 实体与Images 具有OneToMany 关联,所以我不能这样做`foreach($entityInstance->getImageFile() as $file)`因为我的文件在另一个实体中。 @laneboyandrew 在这种情况下,每个文件都会作为不同的实体上传。我一次为一个类别上传许多图像。假设我选择了我的类别,上传 10 个文件并单击保存。这将创建 10 个与同一类别相关的库实体。 getImageFile 未映射,因此它仅临时存储在实体中,因此您可以循环遍历它并实际为每个文件创建一个唯一实体。

以上是关于Symfony 5. EasyAdmin 3. VichUploader。同时上传多个文件的主要内容,如果未能解决你的问题,请参考以下文章

Symfony 5 easyadmin 3 与 ManyToOne 关系的实体 - 不保存在“多”端

Symfony - EasyAdmin - 从 AssociationField 中忽略添加和删除功能

在 Symfony 5 中使用 composer 安装 EasyAdmin 的问题

EasyAdmin 3.1 CrudControllers Symfony

如何在 Symfony EasyAdmin 3 中创建密码输入类型

Symfony 5 - Easy Admin 3:关联实体有这么多数据时,AssociationField 的负载很重