Symfony Doctrine Prevent Particular Entity's Record Deletion

You could do this down at the database level. Just create a table called for example protected_users with foreign key to users and set the key to ON DELETE RESTRICT. Create a record in this table for every user you don't want to delete. That way any attempt to delete the record will fail both in Doctrine as well as on db level (on any manual intervention in db). No edit to users entity itself is needed and it's protected even without Doctrine. Of course, you can make an entity for that protected_users table.

You can also create a method on User entity like isProtected() which will just check if related ProtectedUser entity exists.


You should have a look at the doctrine events with Symfony:

Step1: I create a ProtectedInterface interface with one method:

public function isDeletable(): boolean

Step2: I create a ProtectionTrait trait which create a new property. This isDeletable property is annotated with @ORM/Column. The trait implements the isDeletable(). It only is a getter.

If my entity could have some undeletable data, I update the class. My class will now implement my DeleteProtectedInterface and use my ProtectionTrait.

Step3: I create an exception which will be thrown each time someone try to delete an undeletable entity.

Step4: Here is the tips: I create a listener like the softdeletable. In this listener, I add a condition test when my entity implements the ProtectedInterface, I call the getter isDeleteable():

final class ProtectedDeletableSubscriber implements EventSubscriber
{
    public function onFlush(OnFlushEventArgs $onFlushEventArgs): void
    {
        $entityManager = $onFlushEventArgs->getEntityManager();
        $unitOfWork = $entityManager->getUnitOfWork();

        foreach ($unitOfWork->getScheduledEntityDeletions() as $entity) {
            if ($entity instanceof ProtectedInterface && !$entity->isDeletable()) {
                throw new EntityNotDeletableException();
            }
        }
    }
}

I think that this code could be optimized, because it is called each time I delete an entity. On my application, users don't delete a lot of data. If you use the SoftDeletable component, you should replace it by a mix between this one and the original one to avoid a lot of test. As example, you could do this:

final class ProtectedSoftDeletableSubscriber implements EventSubscriber
{
    public function onFlush(OnFlushEventArgs $onFlushEventArgs): void
    {
        $entityManager = $onFlushEventArgs->getEntityManager();
        $unitOfWork = $entityManager->getUnitOfWork();

        foreach ($unitOfWork->getScheduledEntityDeletions() as $entity) {
            if ($entity instanceof ProtectedInterface && !$entity->isDeletable()) {
                throw new EntityNotDeletableException();
            }

            if (!$entity instance SoftDeletableInterface) {
                 return
            }

            //paste the code of the softdeletable subscriber
        }
    }
}