VichUploader: reupload image on entity update - php

I have entity Asset with field image
/**
* #ORM\Table(name="asset")
* #ORM\Entity(repositoryClass="AppBundle\Repository\AssetRepository")
* #Vich\Uploadable
*/
class Asset
{
/**
* #ORM\Column(name="name", type="string", length=255)
*/
private $name;
/**
* #ORM\ManyToOne(targetEntity="Category", inversedBy="assets")
* #ORM\JoinColumn(name="category_id", referencedColumnName="id")
*/
private $category;
/**
* #ORM\Column(name="image", type="string", length=255, nullable=true)
*/
private $image;
/**
* #Vich\UploadableField(mapping="assets", fileNameProperty="image")
* #var File
*/
protected $imageFile;
I am using VichUploader to upload images to S3 bucket. I created custom namer to create file name. During upload entity is uploaded to folder with category name and is named with entity name
public function name($obj, PropertyMapping $mapping)
{
$category = $obj->getCategory()->getName();
$name = $obj->getName();
$file = $mapping->getFile($obj);
if ($extension = $this->getExtension($file)) {
$name = sprintf('%s.%s', $name, $extension);
}
return $category.'/'.$name;
}
These my upload configurations
oneup_flysystem:
adapters:
assets_adapter:
awss3v3:
client: app.assets.s3
bucket: '%assets_bucket%'
prefix: assets
filesystems:
assets_fs:
adapter: assets_adapter
mount: assets_fs
vich_uploader:
db_driver: orm
storage: flysystem
mappings:
assets:
delete_on_remove: true
delete_on_update: true
uri_prefix: 'https://s3-%assets_region%.amazonaws.com/%assets_bucket%/'
upload_destination: assets_fs
namer: app.asset_namer
I have following situation: user changes category of Asset. How file can be re-uploaded to new category folder and update name?
UPDATE
I am using EasyAdminBundle. Which handles create and edit entities. So I didn't create FormType and Controller for Asset entity. Here are configs:
easy_admin:
entities:
Asset:
class: AppBundle\Entity\Asset
label: 'Assets'
form:
fields:
- name
- {property: 'category', type: entity, type_options: {expanded: false, multiple: false, class: 'AppBundle:Category', required: true}}
- {property: 'imageFile', type: 'vich_image' }

One solution would be to update the Asset::setCategory method logic, to create a new File object when the category name is changed, and pass it to Asset::setImageFile to cause VichUploader to run the update process.
use Symfony\Component\HttpFoundation\File\UploadedFile;
/**
* #ORM\Table(name="asset")
* #ORM\Entity(repositoryClass="AppBundle\Repository\AssetRepository")
* #Vich\Uploadable
*/
class Asset
{
//...
public function setCategory(Category $category)
{
if ($this->imageFile && $this->category->getName() !== $category->getName()) {
$this->setImageFile(new UploadedFile(
$this->imageFile->getRealPath(), //physical path to image file
$this->image, //current image name
null,
null,
null,
true
));
}
$this->category = $category;
return $this;
}
}
One issue is that by default VichUploader does not populate the entity imageFile property.
To ensure Asset::$imageFile is available, without needing to interact with the vich_uploader.storage service, you will need to add inject_on_load: true to your vich_uploader.mappings settings. This will add a listener to the entity to automatically populate the imageFile property with a File object.
#app/config/config.yml
#...
vich_uploader:
db_driver: orm
storage: flysystem
mappings:
assets:
delete_on_remove: true
delete_on_update: true
uri_prefix: 'https://s3-%assets_region%.amazonaws.com/%assets_bucket%/'
upload_destination: assets_fs
namer: app.asset_namer
inject_on_load: true
The prefered method would be to add the logic to your controller edit action, or adding a custom event subscriber to a Symfony event that monitors the Asset entity. Though I am not aware of how to accomplish this in relation to a category name change in EasyAdminBundle.

Related

There is no extension able to load the configuration for "vich_uploader"

I would like to use vich uploader in my easy admin interface. After installing it, I've created the vich_uploader.yaml file (it is not generating alone in my case).
I've follow the tutorial in the symfony page, but when I want to use it, an error occured :
"There is no extension able to load the configuration for "vich_uploader""
I really don't understand why, here is my files :
easy_admin.yaml :
easy_admin:
entities:
Project:
list:
fields:
- { property: 'photo_1', type: 'image', base_path: '%app.path.project_images%' }
show:
fields:
- { property: 'photo_1', type: 'image', base_path: '%app.path.project_images%' }
form:
fields:
- {property: 'photo_1File', type: 'vich_image'}
vich_uploader.yaml :
vich_uploader:
db_driver: orm
mappings:
project_images:
uri_prefix: '%app.path.project_images%'
upload_destination: '%kernel.project_dir%/public%app.path.project_images%'
My project entity looks like :
/**
* #ORM\Entity(repositoryClass=ProjectRepository::class)
* #Vich\Uploadable
*/
class Project
{
/**
* #ORM\Column(type="string", length=255, nullable=true)
* #var string
*/
private $photo_1;
/**
* #Vich\UploadableField(mapping="project_images", fileNameProperty="photo_1")
* #var File
*/
private $photo_1File;
public function getPhoto1(): ?string
{
return $this->photo_1;
}
public function setPhoto1(?string $photo): self
{
$this->photo_1 = $photo;
return $this;
}
public function setPhoto1File(File $image = null)
{
$this->photo_1File = $image;
if ($image) {
$this->updatedAt = new \DateTime('now');
}
}
public function getPhoto1File()
{
return $this->photo_1File;
}
services.yaml :
parameters:
app.path.project_images: /uploads/images/projects
I've the line "vich/uploader-bundle": "^1.15" in my composer.json. It looks like symfony doesn't found the package...
Add to bundles.php:
Vich\UploaderBundle\VichUploaderBundle::class => ['all' => true],
This will activate the bundle for all environment, not only dev and test otherwise it won't be available on live environment

Removing the generated hash when uploading an image with easy_admin / #Vich\Uploadable

My problem is simple but complicated at the same time.
Basically when you upload a image with easy_admin. The image get's a hash.
Like so:
/uploads/images/5f17449f4932f_image004.jpg
Is there any way to remove the generated hash before the image name ?
Here is my Entity:
use Symfony\Component\HttpFoundation\File\File;
use Vich\UploaderBundle\Mapping\Annotation as Vich;
/**
* Image
*
* #ORM\Column(type="string", length=255)
* #var string
*/
private $image = '';
/**
* ImageFile
*
* #Vich\UploadableField(mapping="images", fileNameProperty="image")
* #var File
*/
private $imageFile;
Is there a setting that I may use in the easy_admin.yml config ?
form:
fields:
- { property: 'imageFile', label: 'Image', type: 'vich_image'}
Let me know if any other information is needed.
Thank you.
UPDATE: The class
namespace App\Service\FileNamer;
use Symfony\Component\HttpFoundation\File\UploadedFile;
use Vich\UploaderBundle\Mapping\PropertyMapping;
use Vich\UploaderBundle\Naming\NamerInterface;
class FileNamer implements NamerInterface
{
public function name($object, PropertyMapping $mapping): string
{
/* #var $file UploadedFile */
$file = $mapping->getFile($object);
return $file->getClientOriginalName();
}
}
Easy_admin
db_driver: orm
mappings:
images:
uri_prefix: '%upload_images_folder%'
upload_destination: '%kernel.root_dir%/../public%upload_images_folder%'
namer:
service: App\Service\FileNamer
videos:
uri_prefix: '%upload_videos_folder%'
upload_destination: '%kernel.root_dir%/../public%upload_videos_folder%'
namer:
service: vich_uploader.namer_origname
pdfs:
uri_prefix: '%upload_pdfs_folder%'
upload_destination: '%kernel.root_dir%/../public%upload_pdfs_folder%'
namer:
service: vich_uploader.namer_origname
Create your own custom namer class. Just implement Vich\UploaderBundle\Naming\NamerInterface and add it to vich_uploader configuration.
https://github.com/dustin10/VichUploaderBundle/blob/master/docs/file_namer/howto/create_a_custom_file_namer.md

Symfony 4 - LiipImagine - Listener delete all my directory (cache clear listener)

I have a symfony 4 project with a User entity that has a relationship with an Avatar entity (images uploaded with VichUploaderBundle).
In Avatar.php:
/**
* #ORM\Column(type="string", length=255, nullable=true)
*/
private $imageName;
/**
* NOTE: This is not a mapped field of entity metadata, just a simple property.
*
* #Assert\Image(
* mimeTypes="image/jpeg")
* #Vich\UploadableField(mapping="avatar", fileNameProperty="imageName", size="imageSize")
*
* #var File|null
*/
private $imageFile;
In User.php:
/**
* #ORM\OneToOne(targetEntity="App\Entity\Avatar", mappedBy="user", cascade={"persist", "remove"})
*/
private $avatar;
I have a profile page to edit a user's data (name, surname, mail, avatar).
In this page, I use LiipImagineBundle to display the current avatar in a certain dimension.
When the user edits his profile, I wish a listener can check is there are changes in the avatar. In which case, it deletes the old media / cache.
So I created a Listener for that:
<?php
namespace App\Listener;
use App\Entity\Avatar;
use Doctrine\Common\EventSubscriber;
use Doctrine\ORM\Event\LifecycleEventArgs;
use Doctrine\ORM\Event\PreUpdateEventArgs;
use Liip\ImagineBundle\Imagine\Cache\CacheManager;
use Symfony\Component\HttpFoundation\File\UploadedFile;
use Vich\UploaderBundle\Templating\Helper\UploaderHelper;
class ImageCacheSubscriber implements EventSubscriber
{
/**
* CacheManager
*
* #var CacheManager
*/
private $cacheManager;
/**
* UploaderHelper
*
* #var UploaderHelper
*/
private $uploaderHelper;
public function __construct(CacheManager $cacheManager, UploaderHelper $uploaderHelper)
{
$this->cacheManager = $cacheManager;
$this->uploaderHelper = $uploaderHelper;
}
public function getSubscribedEvents()
{
return [
'preRemove',
'preUpdate'
];
}
public function preRemove(LifecycleEventArgs $args)
{
$entity = $args->getEntity();
if (!$entity instanceof Avatar) {
return;
}
$this->cacheManager->remove($this->uploaderHelper->asset($entity, 'imageFile'));
}
public function preUpdate(PreUpdateEventArgs $args)
{
dump($args->getEntity());
dump($args->getObject());
$entity = $args->getEntity();
if (!$entity instanceof Avatar) {
return;
}
if ($entity->getImageFile() instanceof UploadedFile) {
$this->cacheManager->remove($this->uploaderHelper->asset($entity, 'imageFile'));
}
}
}
Services.yaml:
App\Listener\ImageCacheSubscriber:
tags:
- { name: doctrine.event_subscriber }
But when I change my avatar, the listener removes the entire folder containing the avatars in the media.
And he provokes me this error:
Failed to remove directory
"C:\Users\user\Desktop\congesTest2/public/media/cache/avatar_big\files":
rmdir(C:\Users\user\Desktop\congesTest2/public/media/cache/avatar_big\files):
Directory not empty.
I don't understand why ... :'(
EDIT:
I update my function preUpdate() to postUpdate() :
public function getSubscribedEvents()
{
return [
'preRemove',
'postUpdate'
];
}
public function postUpdate(LifecycleEventArgs $args)
{
dump($args->getEntity());
$entity = $args->getEntity();
if (!$entity instanceof Avatar) {
return;
}
if ($entity->getImageFile() instanceof UploadedFile) {
$this->cacheManager->remove($this->uploaderHelper->asset($entity, 'imageFile'));
}
}
And now, if I make a dump of :
dd($this->uploaderHelper->asset($entity, 'imageFile'));
I've :
"/images/avatar/avatar3.jpg"
And this is the good path ! On the other hand, the image is not removed from the cache! The remove () function does not seem to give anything it's amazing
With the dump of the entity, I saw that the file was no longer an UploadedFile but a File simply. Whereas before it seemed like an UploadedFile. So I changed the line
if ($entity->getImageFile() instanceof UploadedFile)
by
if ($entity->getImageFile() instanceof File)
But the image is still not deleted from the cache.
In my opinion, since this is a postUpdate, it removes the new cache image, not the old one. But since the user is redirected to the same page, he delivers it immediately after caching. (EDIT : No I did a test, the image is not even removed from the cache)
Instead of listening to Doctrine events, you can listen to the vich_uploader.pre_remove event. This will ensure you get the old image that needs to be removed every time. First, make sure your VichUploader config is set to delete files on update and remove. This is the default.
# config/packages/vich_uploader.yaml
vich_uploader:
mappings:
avatar:
upload_destination: '%kernel.project_dir%/public/uploads/avatar'
uri_prefix: 'uploads/avatar'
delete_on_update: true
delete_on_remove: true
Now you need to create the listener.
// src/EventListener/ImageCacheSubscriber.php
namespace App\EventListener;
use Liip\ImagineBundle\Imagine\Cache\CacheManager;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Vich\UploaderBundle\Event\Event;
use Vich\UploaderBundle\Event\Events;
use Vich\UploaderBundle\Storage\StorageInterface;
class ImageCacheSubscriber implements EventSubscriberInterface
{
private $storage;
private $cacheManager;
public function __construct(StorageInterface $storage, CacheManager $cacheManager)
{
$this->storage = $storage;
$this->cacheManager = $cacheManager;
}
public function onRemove(Event $event)
{
$path = $this->storage->resolveUri($event->getObject(), $event->getMapping()->getFilePropertyName());
$this->cacheManager->remove($path);
}
public static function getSubscribedEvents()
{
return [Events::PRE_REMOVE => 'onRemove'];
}
}
When any VichUploader asset is removed, this listener will attempt to remove it from the cache for all filters. You can specify specific filters in the CacheManager::remove() method if you would like. You could also only remove the cache for specific entities by checking the instance of $event->getObject().
This also makes a few assumptions about your LiipImagine config. If you're using the default loader and cache resolver, this should work. If you're using a custom loader or resolver, you may need to modify this listener to your needs.
# config/packages/liip_imagine.yaml
liip_imagine:
resolvers:
default:
web_path:
web_root: '%kernel.project_dir%/public'
cache_prefix: 'media/cache'
loaders:
default:
filesystem:
data_root:
- '%kernel.project_dir%/public'
filter_sets:
cache: ~
# Your filters...
If you're using Symfony Flex, you're done. Otherwise, make sure to register the listener as a service.
# config/services.yaml
services:
# ...
App\EventListener\ImageCacheSubscriber:
arguments: ['#vich_uploader.storage.file_system', '#liip_imagine.cache.manager']
tags:
- { name: kernel.event_subscriber }
As you are using VichUploaderBundle it should not be needed to create your own listener. Make sure the lifecycle events are properly configured and Vich should take care of deleting old images.
# config/packages/vich_uploader.yaml or app/config/config.yml
vich_uploader:
db_driver: orm
mappings:
product_image:
uri_prefix: /images/products
upload_destination: '%kernel.project_dir%/public/images/products'
inject_on_load: false
delete_on_update: true
delete_on_remove: true
https://github.com/dustin10/VichUploaderBundle/blob/master/Resources/doc/usage.md#step-3-configure-the-lifecycle-events-optional-step

EasyAdminBundle and VichUploaderBundle - Error: The expected argument of type"", "Symfony\Component\HttpFoundation\File\UploadedFile" given

I'm using EasyAdminBundle for entity management and to upload images I want to useVichUploaderBundle.
Following the documentation configure the Bundle:
https://github.com/javiereguiluz/EasyAdminBundle/blob/master/Resources/doc/integration/vichuploaderbundle.rst
I do not use annotations but yml as described in the documentation:
https://github.com/dustin10/VichUploaderBundle/blob/master/Resources/doc/mapping/yaml.md
My code looks like this:
//app/config/config.yml
vich_uploader:
db_driver: orm
mappings:
torneo_images:
uri_prefix: '%app.path.torneo_images%'
upload_destination: '%kernel.root_dir%/../web/uploads/images/torneos'
..........
easy_admin:
form:
fields:
- logo
- { property: 'imageFile', type: 'file' }
The yml configuration file:
//BackendBundle/Resources/config/doctrine/Torneos.orm.yml
......
logo:
type: string
nullable: true
length: 255
options:
fixed: false
imageFile:
mapping: torneo_images
filename_property: logo
Add to Entity
//BackendBundle/Entity/Torneos.orm.yml
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\HttpFoundation\File\File;
use Symfony\Component\Validator\Constraints as Assert;
use Vich\UploaderBundle\Mapping\PropertyMapping as Vich;
namespace BackendBundle\Entity;
.......
/**
* #var string
*/
private $logo;
/**
* #var File
*/
private $imageFile;
.......
/**
* Set logo
*
* #param string $logo
*
* #return Torneos
*/
public function setLogo($logo)
{
$this->logo = $logo;
return $this;
}
/**
* Get logo
*
* #return string
*/
public function getLogo()
{
return $this->logo;
}
/**
* If manually uploading a file (i.e. not using Symfony Form) ensure an instance
* of 'UploadedFile' is injected into this setter to trigger the update. If this
* bundle's configuration parameter 'inject_on_load' is set to 'true' this setter
* must be able to accept an instance of 'File' as the bundle will inject one here
* during Doctrine hydration.
*
* #param File|\Symfony\Component\HttpFoundation\File\UploadedFile $image
*
* #return Torneos
*/
public function setImageFile(File $logo = null)
{
$this->imageFile = $logo;
// VERY IMPORTANT:
// It is required that at least one field changes if you are using Doctrine,
// otherwise the event listeners won't be called and the file is lost
//if ($image) {
// if 'updatedAt' is not defined in your entity, use another property
// $this->updatedAt = new \DateTime('now');
//}
return $this;
}
/**
* #return File|null
*/
public function getImageFile()
{
return $this->imageFile;
}
Also add this code (I'm not sure if it's correct)
//BackendBundle/Resources/config/vich_uploader/Torneos.orm.yml
BackendBundle\Entity\Torneos:
imageFile:
mapping: torneo_images
filename_property: logo
Can anyone give me some idea to fix it?
The solution was quite simple.
The error occurs because the use are placed before thenamespace in the controller.
namespace BackendBundle\Entity;
Regards

StofDoctrineExtensionsBundle softdelete - How do I use it?

My boss installed this bundle for the softdelete filter, but the documentation is beyond sparse. How do I use this in my delete queries?
Enable it in your config:
stof_doctrine_extensions:
orm:
default:
...
softdeleteable: true
doctrine:
...
orm:
filters:
softdeleteable:
class: Gedmo\SoftDeleteable\Filter\SoftDeleteableFilter
enabled: true
Then in your entity:
<?php
namespace Foo\BarBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use Gedmo\Mapping\Annotation as Gedmo;
/**
* ...
* #Gedmo\SoftDeleteable(fieldName="deletedAt")
* #ORM\Entity
*/
class Foo
{
/**
* #var \DateTime $deletedAt
*
* #ORM\Column(name="deleted_at", type="datetime", nullable=true)
*/
private $deletedAt;
Then just delete entities like you normally would (the extension takes care of the rest):
$em = $this->getDoctrine()->getManager();
$em->remove($entity);
$em->flush();
I also needed another puzzle part: The doctrine yaml config:
XYBundle\Entity\Adresse:
type: entity
table: adresse
gedmo:
soft_deleteable:
field_name: deleted_at
time_aware: false
id:
id:
type: integer
generator: { strategy: AUTO }
fields:
ort:
type: string
length: 100
plz:
type: string
columnDefinition: varchar(255) NOT NULL DEFAULT ''
deleted_at:
type: datetime
nullable: true

Categories