Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Symfony Doctrine Prevent Particular Entity's Record Deletion

imagine I have some doctrine Entity and I can have some records of this entity in the database which I dont want to be deleted, but I want them to be visible.

In general I can have entities, for which I have default records, which must stay there - must not be deleted, but must be visible.

Or for example, I want to have special User account only for CRON operations. I want this account to be visible in list of users, but it must not be deleted - obviously.

I was searching and best what I get was SoftDeletable https://github.com/Atlantic18/DoctrineExtensions/blob/v2.4.x/doc/softdeleteable.md It prevents fyzical/real deletion from DB, but also makes it unvisible on the Front of the app. It is good approach - make a column in the Entity's respective table column - 1/0 flag - which will mark what can not be deleted. I would also like it this way because it can be used as a Trait in multiple Entities. I think this would be good candidate for another extension in the above Atlantic18/DoctrineExtensions extension. If you think this is good idea (Doctrine filter) what is the best steps to do it?

The question is, is this the only way? Do you have a better solution? What is common way to solve this?

EDIT: 1. So, we know, that we need additional column in a database - it is easy to make a trait for it to make it reusable But 2. To not have any additional code in each repository, how to accomplish the logic of "if column is tru, prevent delete" with help of Annotation? Like it is in SoftDeletable example above.

Thank you in advance.

like image 623
Mirgen Avatar asked Apr 08 '20 21:04

Mirgen


2 Answers

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.

like image 127
Filip Halaxa Avatar answered Nov 16 '22 07:11

Filip Halaxa


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
        }
    }
}
like image 6
Alexandre Tranchant Avatar answered Nov 16 '22 06:11

Alexandre Tranchant