Symfony:使用 dropzone 上传文件
Syfmony: upload files with dropzone
我正在使用 Symfony 4.1 开发一个简单的 CMS 之王。
关于我的问题,我们有 2 个实体:
Post实体:
<?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;
}
}
所以为了添加 Post,我有一个 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) }}
如您所见,文件的 "input" 作为拖放区 css class。
事实上,我的项目包括 oneup_uploader 包,用于 dropzone。
这里是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);
})
});
我的问题是:
- 文件夹中没有保存文件
- Post 实体保存在我的数据库中,但没有保存图片。
我也尝试过不使用OneUploaderBundle,而使用VichUploader:DB中的保存部分是完美的,但我不能link它到dropzone。
有人帮忙吗?
非常感谢 !
你应该传递 upload url
而不是 upload directory
在树枝中生成 url - {{ 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,
});
可能对新访客有用。
您可以使用扩展 Symfony Form 并添加新类型 DropzneType.
的库
1.Install图书馆
composer require emrdev/symfony-dropzone
这样您将拥有一个新的表单类型 DropzoneType
2.Use 在你的表单中这样输入
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);
}
note that uploadhandler should return a File object
我正在使用 Symfony 4.1 开发一个简单的 CMS 之王。
关于我的问题,我们有 2 个实体:
Post实体:
<?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; } }
所以为了添加 Post,我有一个 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) }}
如您所见,文件的 "input" 作为拖放区 css class。 事实上,我的项目包括 oneup_uploader 包,用于 dropzone。
这里是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);
})
});
我的问题是:
- 文件夹中没有保存文件
- Post 实体保存在我的数据库中,但没有保存图片。
我也尝试过不使用OneUploaderBundle,而使用VichUploader:DB中的保存部分是完美的,但我不能link它到dropzone。
有人帮忙吗? 非常感谢 !
你应该传递 upload url
而不是 upload directory
在树枝中生成 url - {{ 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,
});
可能对新访客有用。 您可以使用扩展 Symfony Form 并添加新类型 DropzneType.
的库1.Install图书馆
composer require emrdev/symfony-dropzone
这样您将拥有一个新的表单类型 DropzoneType
2.Use 在你的表单中这样输入
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);
}
note that uploadhandler should return a File object