【问题标题】:Syfmony: upload files with dropzoneSymfony:使用 dropzone 上传文件
【发布时间】:2019-01-12 15:20:21
【问题描述】:

我正在使用 Symfony 4.1 开发简单 CMS 之王。

关于我的问题,我们有 2 个实体:

  • 发布实体:

    <?php
    
    namespace App\Entity;
    
    use Doctrine\Common\Collections\ArrayCollection;
    use Doctrine\Common\Collections\Collection;
    use Doctrine\ORM\Mapping as ORM;
    use Symfony\Component\Validator\Constraints as Assert;
    
    /**
     * @ORM\Entity(repositoryClass="App\Repository\PostRepository")
     */
    class Post extends BaseEntity
    {
        /**
         * @ORM\Id()
         * @ORM\GeneratedValue()
         * @ORM\Column(type="integer")
         */
        private $id;
    
        /**
         * @ORM\Column(type="text")
         */
        private $content;
    
        /**
         * @ORM\Column(type="boolean")
         */
        private $status;
    
        /**
         * @ORM\ManyToMany(targetEntity="App\Entity\Category", inversedBy="posts")
         */
        private $categories;
    
        /**
         * @ORM\OneToMany(targetEntity="App\Entity\Picture", mappedBy="post", orphanRemoval=true, cascade={"persist"})
         */
        private $pictures;
    
        /**
         * @Assert\All({@Assert\Image(mimeTypes="image/jpeg")})
         *
         */
        private $pictureFiles;
    
        /**
         * Post constructor.
         */
        public function __construct()
        {
            $this->categories = new ArrayCollection();
            $this->pictures = new ArrayCollection();
        }
    
        /**
         * @return int|null
         */
        public function getId(): ?int
        {
            return $this->id;
        }
    
        /**
         * @return null|string
         */
        public function getContent(): ?string
        {
            return $this->content;
        }
    
        /**
         * @param string $content
         * @return Post
         */
        public function setContent(string $content): self
        {
            $this->content = $content;
    
            return $this;
        }
    
        /**
         * @return bool|null
         */
        public function getStatus(): ?bool
        {
            return $this->status;
        }
    
        /**
         * @param bool $status
         * @return Post
         */
        public function setStatus(bool $status): self
        {
            $this->status = $status;
    
            return $this;
        }
    
        /**
         * @return Collection|Category[]
         */
        public function getCategories(): Collection
        {
            return $this->categories;
        }
    
        /**
         * @param Category $category
         * @return Post
         */
        public function addCategory(Category $category): self
        {
            if (!$this->categories->contains($category)) {
                $this->categories[] = $category;
            }
    
            return $this;
        }
    
        /**
         * @param Category $category
         * @return Post
         */
        public function removeCategory(Category $category): self
        {
            if ($this->categories->contains($category)) {
                $this->categories->removeElement($category);
            }
    
            return $this;
        }
    
        /**
         * @return Collection|Picture[]
         */
        public function getPictures(): Collection
        {
            return $this->pictures;
        }
    
        /**
         * @param Picture $picture
         * @return Post
         */
        public function addPicture(Picture $picture): self
        {
            if (!$this->pictures->contains($picture)) {
    
                $this->pictures[] = $picture;
                $picture->setPost($this);
            }
    
            return $this;
        }
    
        /**
         * @param Picture $picture
         * @return Post
         */
        public function removePicture(Picture $picture): self
        {
            if ($this->pictures->contains($picture)) {
    
                $this->pictures->removeElement($picture);
    
                if ($picture->getPost() === $this) {
                    $picture->setPost(null);
                }
            }
    
            return $this;
        }
    
        /**
         * @return mixed
         */
        public function getPictureFiles()
        {
            return $this->pictureFiles;
        }
    
        /**
         * @param $pictureFiles
         * @return Post
         */
        public function setPictureFiles($pictureFiles): self
        {
            foreach ($pictureFiles as $pictureFile) {
    
                /** @var Picture $picture */
                $picture = new Picture();
    
                $picture->setImageFile($pictureFile);
                $this->addPicture($picture);
            }
    
            $this->pictureFiles = $pictureFiles;
    
            return $this;
        }
    }
    
  • 图片实体:

    <?php
    
    namespace App\Entity;
    
    use Doctrine\ORM\Mapping as ORM;
    use Symfony\Component\Validator\Constraints as Assert;
    use Symfony\Component\HttpFoundation\File\File;
    
    /**
     * @ORM\Entity(repositoryClass="App\Repository\PictureRepository")
     */
    class Picture
    {
        /**
         * @ORM\Id()
         * @ORM\GeneratedValue()
         * @ORM\Column(type="integer")
         */
        private $id;
    
        /**
         * @var File|null
         * @Assert\Image(mimeTypes="image/jpeg")
         */
        private $imageFile;
    
        /**
         * @ORM\Column(type="string", length=255)
         */
        private $filename;
    
        /**
         * @ORM\ManyToOne(targetEntity="App\Entity\Post", inversedBy="pictures")
         * @ORM\JoinColumn(nullable=false)
         */
        private $post;
    
        /**
         * @return int|null
         */
        public function getId(): ?int
        {
            return $this->id;
        }
    
        /**
         * @return File|null
         */
        public function getImageFile(): ? File
        {
            return $this->imageFile;
        }
    
        /**
         * @param File|null $imageFile
         * @return Picture
         */
        public function setImageFile(? File $imageFile): self
        {
            $this->imageFile = $imageFile;
    
            return $this;
        }
    
        /**
         * @return string|null
         */
        public function getFilename(): ?string
        {
            return $this->filename;
        }
    
        /**
         * @param string $filename
         * @return Picture
         */
        public function setFilename(string $filename): self
        {
            $this->filename = $filename;
    
            return $this;
        }
    
        /**
         * @return Post|null
         */
        public function getPost(): ?Post
        {
            return $this->post;
        }
    
        /**
         * @param Post|null $post
         * @return Picture
         */
        public function setPost(?Post $post): self
        {
            $this->post = $post;
    
            return $this;
        }
    }
    

所以为了添加帖子,我有一个 PostType:

<?php

namespace App\Form;

use App\Entity\Category;
use App\Entity\Post;
use FOS\CKEditorBundle\Form\Type\CKEditorType;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\FileType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;

/**
 * Class PostType
 * @package App\Form
 */
class PostType extends AbstractType
{
    /**
     * @param FormBuilderInterface $builder
     * @param array $options
     */
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder
            ->add('name')
            ->add('content', CKEditorType::class)
            ->add('categories', EntityType::class,
                [
                    'class'        => Category::class,
                    'required'     => true,
                    'choice_label' => 'name',
                    'multiple'     => true,
                ]
            )
            ->add('pictureFiles', FileType::class,
                [
                    'required' => false,
                    'multiple' => true,
                    'label'    => 'Add files...',
                    'attr' =>
                        [
                            'action' => '%kernel.project_dir%/public/media/posts'
                        ]
                ]
            )
            ->add('status')
        ;
    }

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

该表单对应的视图:

{% form_theme form '/admin/form/switch_btn_layout.html.twig' %}

{{ form_start(form) }}

    {{ form_errors(form) }}

    <div class="form-row">

        <div class="col-md-6">
            {{ form_row(form.name) }}
            {{ form_row(form.categories) }}
            {{ form_row(form.status) }}
        </div>

        <div class="col-md-6 dropzone" id="postDropzone">
            {{ form_row(form.pictureFiles, {'attr': {'class': 'dropzone'}} ) }}

            <div class="dropzone-previews" style="border: 1px solid red"></div>
        </div>
    </div>

    <div class="form-group">
        {{ form_row(form.content) }}
    </div>

    <div class="form-group">
        {{ form_row(form.status) }}
    </div>

    {{ form_rest(form) }}

    <button class="btn btn-success btn-lg btn-block" id="postSubmit">
        {{ button_label|default('Save') }}
    </button>

{{ form_end(form) }}

如您所见,文件的“输入”为 dropzone css 类。 事实上,我的项目包括用于 dropzone 的 oneup_uploader 包。

这里是 oneup_uploader 的配置:

oneup_uploader:
    mappings:
        # This is a mapping example, remove it and create your own mappings.
        post_image:
            frontend: dropzone
            namer: oneup_uploader.namer.uniqid
            storage:
                directory: '%kernel.project_dir%/public/media/posts'

还有我的 Dropzone 脚本:

Dropzone.autoDiscover = false;

var postDropzone = new Dropzone('.dropzone', {

    url: '%kernel.project_dir%/public/media/posts',
    // url: 'file/post',
    maxFiles: 10,
    addRemoveLinks: true,
    autoProcessQueue: false,
    uploadMultiple: true,
    parallelUploads: 100,

});

postDropzone.on("addedfile", function (file) {

    file.previewElement.addEventListener("click", function () {
        postDropzone.removeFile(file);
    })
});

我的问题是:

  • 文件夹中没有保存文件
  • 帖子实体保存在我的数据库中,但没有保存图片。

我也尝试不使用 OneUploaderBundle,而使用 VichUploader:DB 中的保存部分非常完美,但我无法将其链接到 dropzone。

有人帮忙吗? 非常感谢 !

【问题讨论】:

    标签: symfony dropzone.js vichuploaderbundle oneupuploaderbundle


    【解决方案1】:

    可能对新访客有用。 您可以使用扩展 Symfony Form 并添加新类型 DropzneType 的库。

    1.安装库

    composer require emrdev/symfony-dropzone
    

    这样你就会有一个新的表单类型 DropzoneType

    2. 像这样在表单中使用类型

    public function buildForm(\Symfony\Component\Form\FormBuilderInterface $builder, array $options)
    { 
    
        // userFiles is OneToMany
        $builder->add('userFiles', DropzoneType::class, [
            'class' => File::class,
            'maxFiles' => 6,
            'uploadHandler'=>'uploadHandler',  // route name
            'removeHandler'=> 'removeHandler'// route name
       ]);
    }
    

    将uploadHandler 和removeHandler 选项更改为您的端点

    3.Route uploadHandler/removeHandler 可能看起来像这样

    /**
     * @Route("/uploadhandler", name="uploadHandler")
     */
    public function uploadhandler(Request $request, ImageUploader $uploader) { 
        $doc = $uploader->upload($request->files->get('file'));  
        $file = new File(); 
        $file->setSrc($doc['src']);
        ...
    
        $this->getDoctrine()->getManager()->persist($file);
        $this->getDoctrine()->getManager()->flush();
        return new JsonResponse($file);
    }
    
    
    /**
     * @Route("/removeHandler/{id}", name="removeHandler")
     */
    public function removeHandler(Request $request,File $file = null) {
        $this->getDoctrine()->getManager()->remove($file);
        $this->getDoctrine()->getManager()->flush();
        return new JsonResponse(true);
    }
    

    注意,uploadhandler 应该返回一个 File 对象

    【讨论】:

    • 见“Explaining entirely code-based answers”。虽然这在技术上可能是正确的,但它并不能解释为什么它可以解决问题或应该是选择的答案。我们应该在帮助解决问题的同时进行教育。
    【解决方案2】:

    你应该通过upload url 而不是upload directory

    在树枝中生成网址 - {{ oneup_uploader_endpoint('post_image') }}

    var postDropzone = new Dropzone('.dropzone', {
        url: '{{ oneup_uploader_endpoint('post_image') }}',
        // url: '%kernel.project_dir%/public/media/posts',
        // url: 'file/post',
        maxFiles: 10,
        addRemoveLinks: true,
        autoProcessQueue: false,
        uploadMultiple: true,
        parallelUploads: 100,
    
    });
    

    【讨论】:

      猜你喜欢
      • 2023-03-27
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-09-20
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-10-03
      相关资源
      最近更新 更多