There is a problem when using the SoftDeletable Doctrine extension for entities that have some unique index columns. The most obvious example is User entities. An User has at least one column that needs to be unique (username and/or email). When soft-deleting an User the actual record will stay in the db table. Now if you try to create a new one with the same username/email, the validation will pass (doctrine will not see the existing soft-deleted one) but the database layer will not be able to save the new record:

SQLSTATE[23000]: Integrity constraint violation: 1062 Duplicate entry ... for key …

The first, most simple and obvious solution, is to add code in the controller to change the unique columns before deleting the entity:

$entity->setUsername($entity->getUsername() . "_deleted");
$entity->setEmail($entity->getEmail() . "_deleted");

But what if you have (or add in the future) another place where you delete this kind of entity? Could we make this functionality more general?

We will create a doctrine listener that will add “_deleted” to the username/email column every time an entity is soft-deleted. The Gedmo SoftDeletable Doctrine extension triggers a “preSoftDelete” event and we will use that:

        class: AcmeUserBundleEventListenerSoftDeleteListener
            - { name: doctrine.event_listener, event: preSoftDelete }

namespace AcmeUserBundleEventListener;

use DoctrineORMEventLifecycleEventArgs;
use AcmeUserBundleEntityUser;

class SoftDeleteListener {

    public function preSoftDelete(LifecycleEventArgs $args)
        $entity = $args->getEntity();
        $entityManager = $args->getEntityManager();

        // we only want to act on some "User" entity
        if ($entity instanceof User) {
            // change the unique columns
            if(strpos($entity->getUsername(), "_deleted_") === false) {
                $entity->setUsername($entity->getUsername() . "_deleted_" . time());
                $entity->setEmail($entity->getEmail() . "_deleted_" . time());

You can now remove the code added to your controller, clear the cache, and test the new functionality.

Please let us know in the comments what you think of this and if you have better solutions.