dustin10 / VichUploaderBundle

A simple Symfony bundle to ease file uploads with ORM entities and ODM documents.
MIT License
1.83k stars 519 forks source link

Entity association not deleted when removing file #1414

Closed bastien70 closed 9 months ago

bastien70 commented 9 months ago

Bug Report

Q A
BC Break no
Bundle version 2.2.0
Symfony version 6.3.8
PHP version 8.1.16

Summary

There is a problem that has not been resolved for several years.

When we use a generic entity (for example, called "Media"), and we use it as a relationship for all our entities requiring the use of files, then even if we implement a delete cascade on the relationship, if we delete the file, the record of the generic entity is not deleted (or rather, it is recreated directly with the creation and edition dates, without file, and is again associated with the entity)

See https://github.com/dustin10/VichUploaderBundle/issues/1172

How to reproduce

#[ORM\Entity(repositoryClass: MediaRepository::class)]
#[ORM\HasLifecycleCallbacks]
#[Vich\Uploadable]
class Media implements \Stringable
{
    use PrimaryKeyTrait;
    use CreatedAtTrait;
    use UpdatedAtTrait;

    #[ORM\Column(type: Types::INTEGER, nullable: true)]
    private ?int $size = null;

    #[ORM\Column(type: Types::STRING, length: 255, nullable: true)]
    private ?string $mimeType = null;

    #[ORM\Column(type: Types::STRING, length: 255, nullable: true)]
    private ?string $originalName = null;

    #[ORM\Column(type: Types::STRING, length: 255, nullable: true)]
    private ?string $fileName = null;

    #[Vich\UploadableField(
        mapping: 'media',
        fileNameProperty: 'fileName',
        size: 'size',
        mimeType: 'mimeType',
        originalName: 'originalName'
    )]
    private ?File $file = null;

    public function __construct()
    {
        $this->createdAt = new \DateTimeImmutable();
        $this->updatedAt = new \DateTimeImmutable();
    }

    public function setFile(File $file = null): self
    {
        $this->file = $file;

        if ($file) {
            $this->updatedAt = new \DateTimeImmutable();
        }

        return $this;
    }

    public function getFile(): ?File
    {
        return $this->file;
    }

    // other getters/setters...
}

And I associate it with my entities. By example, in my User entity, I've an avatar field :

#[ORM\OneToOne(cascade: ['persist', 'remove'], fetch: 'EAGER')]
    private ?Media $avatar = null;

In the vich_uploader.yaml :

vich_uploader:
    db_driver: orm
    storage: flysystem
    mappings:
        media:
            upload_destination: 'media.storage'
            namer: Vich\UploaderBundle\Naming\SmartUniqueNamer
            inject_on_load: true
            delete_on_update: true
            delete_on_remove: true

When clicking inside the checkbox and submitting the form to remove current avatar relation, the file is deleted, the Media entity is truncated, but it creates a new media (with just createdAt and updatedAt values) instead of just deleting by the way the "media" row in database

UPDATE :

I found a solution to make it work. In my entities having a relationship with Media, I add the ORM\JoinColumn(onDelete: 'SET NULL')

And I set up an event when a Media entity is modified which checks that we still have a filename. If not, I delete the Media.

    #[ORM\OneToOne(cascade: ['persist', 'remove'])]
    #[ORM\JoinColumn(onDelete: 'SET NULL')]
    private ?Media $avatar = null;
<?php

declare(strict_types=1);

namespace App\EventListener;

use App\Entity\Media;
use Doctrine\Bundle\DoctrineBundle\Attribute\AsDoctrineListener;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\Event\LifecycleEventArgs;
use Doctrine\ORM\Events;

#[AsDoctrineListener(
    event: Events::postUpdate
)]
class MediaEventSubscriber
{
    public function __construct(
        private readonly EntityManagerInterface $manager
    ) {
    }

    public function postUpdate(LifecycleEventArgs $event): void
    {
        $media = $event->getObject();

        if ($media instanceof Media) {
            if (!$media->getFileName()) {
                $this->manager->remove($media);
                $this->manager->flush();
            }
        }
    }
}
garak commented 9 months ago

So is this the same issue as #1172 or a solution for it? Or is it a new issue?

bastien70 commented 9 months ago

It's basically the same problem, but brought up to date, so that it appears in recent issues.

And a possible solution

garak commented 9 months ago

You can bring it up by commenting on it. Closing as a duplicate