Skip to content

Instantly share code, notes, and snippets.

@acecconato
Last active October 10, 2020 21:30
Show Gist options
  • Save acecconato/2867b10b6806f4bd5e815c997d73bfa9 to your computer and use it in GitHub Desktop.
Save acecconato/2867b10b6806f4bd5e815c997d73bfa9 to your computer and use it in GitHub Desktop.
Symfony + EasyAdmin: File upload system
In the constructor, $assetsHelper, FileUploader $fileUploader, $imageUploadPath are autowired by services.yamlwe use
*/
# Default configuration for use with the Gist code
easy_admin:
entities:
Image:
class: App\Entity\Image
form:
fields:
- { property: 'file', type: 'file' }
list:
fields:
- 'id'
- { property: 'web_view', type: 'image', label: 'Thumbnail' }
- { property: 'tempFilename', label: 'Filename' }
# Filename is null due to the setFilename(null) in the postLoad events. We use tempFilename instead.
/**
Due to best practices, we need to create a new service which is used by the ImageListener.
Its role is to upload files and generate an unique filename.
NOTA:
The default $uploadPath is autowired by argument binding inside services.yaml
but in this case, we override it with the $path argument of the upload method.
*/
<?php
namespace App\Service;
use Symfony\Component\HttpFoundation\File\UploadedFile;
class FileUploader
{
private $targetPath;
public function __construct($uploadPath)
{
$this->targetPath = $uploadPath;
}
public function upload(UploadedFile $file, $path = null): string
{
if (null === $path) {
$path = $this->targetPath;
}
$filename = $this->generateUniqueName($file);
# Fix the missing image/svg mimetype in Symfony
if ($file->getMimeType() === "image/svg") {
$filename .= "svg";
}
$file->move($path, $filename);
return $filename;
}
public function generateUniqueName(UploadedFile $file): string
{
return md5(uniqid()).".".$file->guessExtension();
}
}
In the constructor, $assetsHelper, FileUploader $fileUploader, $imageUploadPath are autowired by services.yamlwe use
*/
/**
A simple image entity.
*/
<?php
namespace App\Entity;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\HttpFoundation\File\File;
use Symfony\Component\Validator\Constraints as Asserts;
/**
* @ORM\Entity(repositoryClass="App\Repository\ImageRepository")
* @ORM\EntityListeners({"App\EntityListener\ImageListener"})
* @ORM\HasLifecycleCallbacks()
*/
class Image
{
/**
* @ORM\Id()
* @ORM\GeneratedValue()
* @ORM\Column(type="integer")
*/
private $id;
/**
* Auto generated by entity listener
* @ORM\Column(type="string", length=255)
*/
private $filename;
/**
* Auto generated by entity listener
* @var string
*/
private $tempFilename;
/**
* @var File
* @Asserts\Image()
* @Asserts\NotBlank()
*/
private $file;
/**
* @var string
*/
private $webView;
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 getFile(): ?File
{
return $this->file;
}
public function setFile(File $file = null): self
{
$this->file = $file;
return $this;
}
public function getTempFilename(): ?string
{
return $this->tempFilename;
}
public function setTempFilename($filename): self
{
$this->tempFilename = $filename;
return $this;
}
public function getWebView(): string
{
return $this->webView;
}
public function setWebView(string $webView): self
{
$this->webView = $webView;
return $this;
}
public function __toString()
{
return $this->getFilename();
}
}
/**
The image listener intercepts all events we need:
- postLoad: Define the webview, tempFilename and reset the filename for update's events.
- prePersist: Upload the image and create an unique filename.
- preUpdate: Update the new image and the filename, remove the old one.
- preRemove: Remove the image.
In the constructor, $assetsHelper, FileUploader $fileUploader, $imageUploadPath are autowired by services.yaml
*/
<?php
namespace App\EntityListener;
use App\Entity\Image;
use App\Service\FileUploader;
use Doctrine\ORM\Event\LifecycleEventArgs;
use Doctrine\ORM\Event\PreUpdateEventArgs;
use Symfony\Component\Asset\Package;
class ImageListener
{
/**
* @var Package
*/
private $assetsHelper;
/**
* @var FileUploader
*/
private $fileUploader;
private $imageUploadPath;
private $image;
public function __construct($assetsHelper, FileUploader $fileUploader, $imageUploadPath)
{
$this->assetsHelper = $assetsHelper;
$this->fileUploader = $fileUploader;
$this->imageUploadPath = $imageUploadPath;
}
public function postLoad(Image $image, LifecycleEventArgs $args): void
{
$image->setWebView(
$this->assetsHelper->getUrl("/uploads/images/" . $image->getFilename())
);
if (!$image->getTempFilename()) {
$image->setTempFilename($image->getFilename());
// Enable update events to be triggered
$image->setFilename(null);
}
}
public function prePersist(Image $image, LifecycleEventArgs $args)
{
$this->image = $args->getEntity();
$filename = $this->fileUploader->upload($this->image->getFile(), $this->imageUploadPath);
$this->image->setFilename($filename);
}
public function preUpdate(Image $image, PreUpdateEventArgs $args)
{
$this->image = $args->getEntity();
$filename = $this->fileUploader->upload($this->image->getFile(), $this->imageUploadPath);
$this->image->setFilename($filename);
unlink($this->imageUploadPath . "/" . $this->image->getTempFilename());
}
public function preRemove(Image $image, LifecycleEventArgs $args)
{
$this->image = $args->getEntity();
unlink($this->imageUploadPath . "/" . $this->image->getTempFilename());
}
}
# Here, we define all we need for configuring properly the argument binding, services and listeners
parameters:
server_upload_default_path: '%kernel.project_dir%/public/uploads'
server_upload_image_path: '%kernel.project_dir%/public/uploads/images'
services:
_defaults:
# [...]
bind:
$uploadPath: '%server_upload_default_path%'
$imageUploadPath: '%server_upload_image_path%'
$assetsHelper: '@assets.packages'
# [...]
app.file_uploader:
class: App\Service\FileUploader
image_listener:
class: App\EntityListener\ImageListener
tags:
- { name: doctrine.orm.entity_listener }
# Autowiring
App\Service\FileUploader: '@app.file_uploader'
@featuriz
Copy link

In symfony5 doc about image upload is different. Doctrine event are not recommended in that doc. So how to upload files in easy admin? So it needs to be set on forms.

@acecconato
Copy link
Author

acecconato commented Apr 29, 2020

Wow i'm so sorry, I didn't upload a file on the new version yet but i guess you can refer to : https://symfony.com/doc/current/controller/upload_file.html

In the example, they've done it directly inside the controller without using Doctrine Event Listener. Let the link of your Gist if you manage to do it, good luck and sorry for the outdated practice!

Edit: That's can help you too with EasyAdmin and controller overriding if necessary : https://symfony.com/doc/2.x/bundles/EasyAdminBundle/book/complex-dynamic-backends.html

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment