ethsam/symfony-dropzone 问题修复 & 功能扩展

解决BUG、新增功能、兼容多环境部署,快速响应你的开发需求

邮箱:yvsm@zunyunkeji.com | QQ:316430983 | 微信:yvsm316

ethsam/symfony-dropzone

Composer 安装命令:

composer require ethsam/symfony-dropzone

包简介

Symfony Form Type integrating Dropzone.js for drag-and-drop file uploads with entity relationship support

README 文档

README

Latest Version on Packagist License PHP Version

Seamless integration of Dropzone.js into Symfony Forms with automatic entity relationship management for drag-and-drop file uploads.

EN | FR | ES

Features

  • Drag-and-drop file uploads — Powered by Dropzone.js
  • Entity relationship support — Automatically manage OneToMany and ManyToOne associations
  • Built-in data transformation — IDs to entities via Doctrine ORM
  • Pre-populated edit forms — Show existing files in edit mode
  • Fully configurable — Dropzone.js options exposed in form builder
  • Single or multiple files — Control upload mode per form field
  • Custom upload/remove handlers — Route-based endpoints with JSON responses
  • Image resizing — Client-side image processing before upload
  • Flexible authentication — Custom headers for API integration
  • Symfony Flex compatible — Automatic bundle registration

Requirements

  • PHP: ≥8.1
  • Symfony: 5.4, 6.x, 7.x
  • Doctrine ORM: 2.12+
  • Dropzone.js: 6.0+ (included via CDN)

Installation

Step 1: Install via Composer

composer require ethsam/symfony-dropzone

The bundle registers automatically with Symfony Flex. If you're not using Flex, add to config/bundles.php:

Ethsam\SymfonyDropzone\SymfonyDropzoneBundle::class => ['all' => true],

Step 2: Include Dropzone.js

Add the following to your base template (e.g., base.html.twig):

<link href="https://unpkg.com/dropzone@6.0.0-beta.2/dist/dropzone.css" rel="stylesheet" type="text/css" />
<script src="https://unpkg.com/dropzone@6.0.0-beta.2/dist/dropzone-min.js"></script>

That's it! You're ready to use DropzoneType in your forms.

Quick Start

1. Define Your File Entity

namespace App\Entity;

use Doctrine\ORM\Mapping as ORM;

#[ORM\Entity]
class Attachment
{
    #[ORM\Id]
    #[ORM\GeneratedValue]
    #[ORM\Column]
    private ?int $id = null;

    #[ORM\Column(length: 255)]
    private string $filename = '';

    #[ORM\Column(length: 255)]
    private string $src = ''; // URL or path to file

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

    public function getFilename(): string
    {
        return $this->filename;
    }

    public function setFilename(string $filename): self
    {
        $this->filename = $filename;
        return $this;
    }

    public function getSrc(): string
    {
        return $this->src;
    }

    public function setSrc(string $src): self
    {
        $this->src = $src;
        return $this;
    }
}

2. Define Your Main Entity with Relationship

namespace App\Entity;

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

#[ORM\Entity]
class Post
{
    #[ORM\Id]
    #[ORM\GeneratedValue]
    #[ORM\Column]
    private ?int $id = null;

    #[ORM\Column(length: 255)]
    private string $title = '';

    // OneToMany relationship
    #[ORM\OneToMany(targetEntity: Attachment::class, mappedBy: 'post', cascade: ['persist', 'remove'])]
    private Collection $attachments;

    public function __construct()
    {
        $this->attachments = new ArrayCollection();
    }

    public function addAttachment(Attachment $attachment): self
    {
        if (!$this->attachments->contains($attachment)) {
            $this->attachments->add($attachment);
        }
        return $this;
    }

    public function removeAttachment(Attachment $attachment): self
    {
        $this->attachments->removeElement($attachment);
        return $this;
    }

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

3. Create a Form Type

namespace App\Form;

use App\Entity\Attachment;
use App\Entity\Post;
use Ethsam\SymfonyDropzone\Form\DropzoneType;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;

class PostFormType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options): void
    {
        $builder
            ->add('title', TextType::class, [
                'label' => 'Post Title',
            ])
            ->add('attachments', DropzoneType::class, [
                'class' => Attachment::class,
                'maxFiles' => 5,
                'multiple' => true,
                'uploadHandler' => 'app_upload_file',
                'removeHandler' => 'app_remove_file',
                'acceptedFiles' => 'image/*,.pdf',
                'addRemoveLinks' => true,
            ]);
    }

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

4. Create Upload/Remove Handlers

namespace App\Controller;

use App\Entity\Attachment;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\Attribute\Route;

class FileController extends AbstractController
{
    #[Route('/upload', name: 'app_upload_file', methods: ['POST'])]
    public function upload(Request $request, EntityManagerInterface $em): JsonResponse
    {
        $uploadedFile = $request->files->get('file');

        if (!$uploadedFile) {
            return new JsonResponse(['error' => 'No file provided'], 400);
        }

        // Move the file to your uploads directory
        $filename = uniqid() . '.' . $uploadedFile->guessExtension();
        $uploadedFile->move(
            $this->getParameter('kernel.project_dir') . '/public/uploads',
            $filename
        );

        // Create and persist the attachment
        $attachment = new Attachment();
        $attachment->setFilename($uploadedFile->getClientOriginalName());
        $attachment->setSrc('/uploads/' . $filename);

        $em->persist($attachment);
        $em->flush();

        return new JsonResponse(['id' => $attachment->getId()]);
    }

    #[Route('/remove/{id}', name: 'app_remove_file', methods: ['DELETE'])]
    public function remove(Attachment $attachment, EntityManagerInterface $em): JsonResponse
    {
        $id = $attachment->getId();

        // Optionally delete the file from disk
        // unlink($this->getParameter('kernel.project_dir') . '/public' . $attachment->getSrc());

        $em->remove($attachment);
        $em->flush();

        return new JsonResponse(['id' => $id]);
    }
}

5. Use the Form in Your Template

{# templates/post/create.html.twig #}
{% extends 'base.html.twig' %}

{% block content %}
    <h1>Create Post</h1>

    {{ form_start(form) }}
        {{ form_widget(form.title) }}
        {{ form_widget(form.attachments) }}
        <button type="submit">Create</button>
    {{ form_end(form) }}
{% endblock %}

That's it! The bundle handles everything:

  • Dropzone.js widget rendering
  • File upload via AJAX
  • File ID storage in hidden fields
  • Entity relationship transformation on form submission

Configuration Options

Option Type Default Description
class string null Required. Entity class for file/attachment objects
multiple bool true Enable multiple file mode; set false for single file (ManyToOne)
maxFiles int 1 Maximum number of files allowed in the dropzone
uploadHandler string null Required. Symfony route name for file upload endpoint
removeHandler string null Required. Symfony route name for file removal endpoint
uploadHandlerMethod string "POST" HTTP method for upload requests
removeHandlerMethod string "DELETE" HTTP method for remove requests
choice_src string "src" Entity property name containing file URL/path (getter method: get{PropertyName}())
acceptedFiles string null MIME types accepted (e.g., "image/*,.pdf")
addRemoveLinks bool true Show "Remove" link on file previews
headers array [] Custom HTTP headers sent with requests (e.g., ['Authorization' => 'Bearer TOKEN'])
formData array [] Additional form data sent with upload request
withCredentials int 0 XHR withCredentials setting (0 or 1)
thumbnailWidth int 120 Width of preview thumbnails in pixels
thumbnailHeight int 120 Height of preview thumbnails in pixels
thumbnailMethod string "crop" Thumbnail scaling: "crop" or "contain"
resizeWidth int null Client-side resize width before upload (preserves aspect ratio if only one set)
resizeHeight int null Client-side resize height before upload
resizeMimeType string null Output MIME type after resize (e.g., "image/jpeg")
resizeMethod string "contain" Resize scaling: "crop" or "contain"
filesizeBase int 1024 Base unit for filesize calculations
ignoreHiddenFiles bool true Ignore hidden files in directories
autoProcessQueue bool true Auto-process upload queue on file addition
autoQueue bool true Auto-queue files when added to dropzone
previewsContainer string null CSS selector for custom preview container (e.g., "#my-previews")
required bool true Field is required for form validation

File Entity Requirements

Your file/attachment entity must implement:

  • getId(): ?int — Returns the unique identifier
  • getFilename(): string — Returns the filename for display
  • Getter for choice_src property — By default getSrc(): string, returns the file URL/path for thumbnail display

Example minimal entity:

#[ORM\Entity]
class Attachment
{
    #[ORM\Id]
    #[ORM\GeneratedValue]
    #[ORM\Column]
    private ?int $id = null;

    #[ORM\Column(length: 255)]
    private string $filename = '';

    #[ORM\Column(length: 255)]
    private string $src = '';

    public function getId(): ?int { return $this->id; }
    public function getFilename(): string { return $this->filename; }
    public function setFilename(string $filename): self { $this->filename = $filename; return $this; }
    public function getSrc(): string { return $this->src; }
    public function setSrc(string $src): self { $this->src = $src; return $this; }
}

How It Works

Architecture Overview

  1. Form Type RegistrationDropzoneType extends Symfony's form system
  2. Hidden Fields — For multiple files: a CollectionType with hidden inputs; for single: an EntityType field
  3. Twig Template — Renders Dropzone.js widget with JavaScript configuration
  4. Upload Flow:
    • User drags files or clicks to select
    • Dropzone.js sends each file to your uploadHandler route via AJAX
    • Handler persists entity to database, returns {"id": <int>}
    • Bundle stores file ID in hidden form field
  5. Form Submission — Hidden field values are collected
  6. Data TransformationDropzoneTransformer converts IDs back to entity objects via Doctrine
  7. Persistence — Form submission handles OneToMany/ManyToOne relationships automatically

File Removal Flow

  1. User clicks "Remove" link on file preview
  2. Dropzone.js sends DELETE (or POST) to removeHandler route
  3. Handler deletes entity, returns {"id": <int>}
  4. Widget removes preview from DOM
  5. On next form submission, removed ID is not included, relationship is updated

Examples

Basic Multiple File Upload

$builder->add('attachments', DropzoneType::class, [
    'class' => Attachment::class,
    'multiple' => true,
    'maxFiles' => 10,
    'uploadHandler' => 'app_upload_file',
    'removeHandler' => 'app_remove_file',
]);

Single File Upload (ManyToOne)

$builder->add('profileImage', DropzoneType::class, [
    'class' => ProfileImage::class,
    'multiple' => false,  // Single file mode
    'maxFiles' => 1,
    'uploadHandler' => 'app_upload_image',
    'removeHandler' => 'app_remove_image',
]);

Image-Only with Custom Dimensions

$builder->add('photos', DropzoneType::class, [
    'class' => Photo::class,
    'acceptedFiles' => 'image/*',
    'maxFiles' => 5,
    'uploadHandler' => 'app_upload_photo',
    'removeHandler' => 'app_remove_photo',
    'thumbnailWidth' => 200,
    'thumbnailHeight' => 200,
    'thumbnailMethod' => 'contain',
    'resizeWidth' => 1920,
    'resizeHeight' => 1080,
    'resizeMethod' => 'contain',
    'resizeMimeType' => 'image/jpeg',
]);

With Custom Headers (API Authentication)

$builder->add('documents', DropzoneType::class, [
    'class' => Document::class,
    'uploadHandler' => 'api_upload_document',
    'removeHandler' => 'api_remove_document',
    'headers' => [
        'Authorization' => 'Bearer ' . $this->apiToken,
    ],
    'formData' => [
        'documentType' => 'invoice',
    ],
]);

Custom Preview Container

{# In template #}
<div id="my-previews"></div>

{{ form_start(form) }}
    {{ form_widget(form.documents) }}
{{ form_end(form) }}

{# In form builder #}
$builder->add('documents', DropzoneType::class, [
    'class' => Document::class,
    'uploadHandler' => 'app_upload_document',
    'removeHandler' => 'app_remove_document',
    'previewsContainer' => '#my-previews',
]);

With Custom Form Data

$builder->add('uploads', DropzoneType::class, [
    'class' => Upload::class,
    'uploadHandler' => 'app_upload_file',
    'removeHandler' => 'app_remove_file',
    'formData' => [
        'category' => 'documents',
        'userId' => $this->currentUser->getId(),
    ],
]);

Your upload handler receives this in $request->request->all():

public function upload(Request $request, EntityManagerInterface $em): JsonResponse
{
    $category = $request->request->get('category'); // 'documents'
    $userId = $request->request->get('userId');
    $file = $request->files->get('file');
    // ... handle upload
}

Upgrading from v1

If you're upgrading from the original emr-dev/symfony-dropzone:

  • Bundle namespace changedEthsam\SymfonyDropzone (was EmrDev\SymfonyDropzoneBundle)
  • Form type import — Update: use Ethsam\SymfonyDropzone\Form\DropzoneType;
  • Option names — No changes; all options are backward compatible
  • PHP requirement — Now requires PHP ≥8.1
  • Symfony support — Now supports Symfony 5.4, 6.x, 7.x
  • Data transformer — Automatic; no manual entity conversion needed

Migration example:

// Before (v1)
use EmrDev\SymfonyDropzoneBundle\Form\DropzoneType;

// After (v2)
use Ethsam\SymfonyDropzone\Form\DropzoneType;

The API and functionality remain the same.

Differences from symfony/ux-dropzone

Feature ethsam/symfony-dropzone symfony/ux-dropzone
Entity relationships Full OneToMany/ManyToOne support None; form values only
Data transformation Automatic ID → Entity Manual
Multiple files Built-in with CollectionType Not ideal
Edit mode pre-population Yes; shows existing files Manual templating
Upload handler Simple route + JSON response Requires UX component
File removal Built-in DELETE handler Manual
Dropzone config Full access to all options Limited
Learning curve Minimal; standard Symfony forms Moderate; UX paradigm
Maintenance Active Official but UX-focused

Summary: Use ethsam/symfony-dropzone for entity-driven file management; use symfony/ux-dropzone if you need tight Stimulus integration or prefer the UX paradigm.

Contributing

We welcome contributions! Please:

  1. Fork the repository
  2. Create a feature branch: git checkout -b feat/your-feature
  3. Commit your changes: git commit -m "feat: add your feature"
  4. Push to the branch: git push origin feat/your-feature
  5. Open a Pull Request

For bug reports or feature requests, please open an issue.

License

This bundle is licensed under the MIT License. See LICENSE for details.

Originally forked from emr-dev/symfony-dropzone.

Credits

ethsam/symfony-dropzone 适用场景与选型建议

ethsam/symfony-dropzone 是一款 基于 PHP 开发的 Composer 扩展包,目前已累计 2.25k 次下载、GitHub Stars 达 1, 最近一次更新时间为 2022 年 08 月 22 日, 在 PHP 生态内属于活跃度较高的组件。

它主要适用于以下技术方向: 「symfony」 「form」 「upload」 「dropzone」 「file-upload」 「drag-and-drop」 等业务场景。在实际项目中,围绕这些方向常见需要落地的问题包括:接口对接、性能调优、并发安全、与既有框架(Laravel / ThinkPHP / Yii / Webman 等)的兼容适配,以及生产环境的日志埋点与稳定性保障。

我们在过去多个企业项目中使用过 ethsam/symfony-dropzone 或与其功能相近的方案,如果你在选型或落地过程中遇到问题,例如 版本兼容、二次改造、私有化封装、与内部系统对接、生产 BUG 排查,欢迎联系我们协助评估。

围绕 ethsam/symfony-dropzone 我们能提供哪些服务?
定制开发 / 二次开发

基于 ethsam/symfony-dropzone 在你已有业务上做功能扩展、字段裁剪、UI 适配、与内部账号 / 权限 / 日志系统的深度对接。

BUG 修复 & 性能优化

线上偶发问题、内存泄漏、慢查询、并发异常等排查修复;针对高流量场景做缓存、队列、索引层面的调优。

项目外包 & 长期维护

承接完整的项目从需求 → 设计 → 开发 → 上线 → 长期运维;也可按月提供技术保姆服务。

yvsm@zunyunkeji.com QQ:316430983 微信:yvsm316 西安尊云信息科技 · 专注 PHP / Go / 分布式系统研发

统计信息

  • 总下载量: 2.25k
  • 月度下载量: 0
  • 日度下载量: 0
  • 收藏数: 1
  • 点击次数: 21
  • 依赖项目数: 0
  • 推荐数: 0

GitHub 信息

  • Stars: 1
  • Watchers: 1
  • Forks: 2
  • 开发语言: PHP

其他信息

  • 授权协议: MIT
  • 更新时间: 2022-08-22