Soft delete principals
When using soft-deletion techniques at some point you may stand before a decision what to do with related entities. Imagine a situation like this:
in such case if you need to remove Manufacturer for whatsoever reason you need to decide what should happen to all Cars in the relation. What approach is to simply make the field nullable and allow no relation:
Another approach would be to switch to a different entry or force the user who is performing the deletion in any form of a CRUD interface to select a different one. More advanced solutions involve full data versioning: so that each such change really creates a new dataset of information with previous revision. NoSQL approach of storing payment transactions for instance often has redundant data so that any related entity removal does not cause any harm in terms of integrity.
Soft-deletion comes here handy as it basically means that we are not removing the object, but marking is at removed therefore data is integral, yet this has its own drawbacks: for example duplicated keys, if you have any textual key you may encounter a situation in which database will not allow you to create a new entity to avoid collision, duplication of the key. A simple solution for that is to add the date, numerical etc. parameter which indicates that record has been soft-deleted into the same unique constraint as a compound key. Bigger problem is understanding how that impacts your business logic: for instance if you have a screen which displays Car then you need handle lack of Manufacturer even if it is still there, but soft deleted: do not retrieve such entries at all from the database with a simple WHERE deleted_at is null
formula or by retrieving it and later handling it in the layers of your application between before transformation into a model or in worst cases later, in the controller or even view. Not even mentioning topics related to GDPR here which involve anonymization of the data. All of this may be an even greater challenge if you are using any ORM libraries which cannot be exactly instructed how to behave in such scenarios. The case below is about the latter.
Warning!
Library described in this article should be used only in critical situations where a better approach, like versioning or forcing user to change, cannot be instantly added, but the ability to delete related entities is already required.
Problem with symfony/form and EntityType
A popular approach to implement soft deletion with Symfony Framework is to use Doctrine Extensions library. You simply install it using composer require gedmo/doctrine-extensions
, then you enable the doctrine listener:
stof_doctrine_extensions:
orm:
default:
timestampable: true
instruct specific connection to use that listener:
doctrine:
dbal
orm:
filters:
softdeleteable:
class: Gedmo\SoftDeleteable\Filter\SoftDeleteableFilter
enabled: true
and later just mark entities which you want to use soft deletion with specific attribute and mark the class as implementing an interface provided by the lib
use Gedmo\SoftDeleteable\SoftDeleteable;
use Gedmo\SoftDeleteable\Traits\SoftDeleteableEntity;
#[ORM\Entity(repositoryClass: DeletableEntityRepository::class)]
#[Gedmo\SoftDeleteable(fieldName: 'deletedAt', timeAware: false, hardDelete: false)]
class DeletableEntity implements SoftDeleteable
and that is all, any attempt to remove using $entityManager->remove(...);
will simply set the deletedAt
field which makes the entity invisible to future calls of the same entity manager (find
, findOneBy
, createQueryBuilder
etc.).
Sounds great, right? Well, you still need to consider everything mentioned in the first part of this article. Sadly that becomes already a problem if you have any form with EntityType
. For instance take this form for creation and editing of a class which has two fields: name and a relation to another entity called DeletableEntity
which can be soft deleted:
$builder
->add('name')
->add('relatedDeletableEntity', EntityType::class, [
'class' => DeletableEntity::class,
'placeholder' => 'placeholder',
'choice_label' => 'name',
])
;
What will happen if you try to edit a record which has its associated DeletableEntity
?
so exactly the same as if the entity would be already removed, yet we can use the fact that we actually know that it is not removed for our advantage and re-use exisitng form implementation to not crash in such scenarios, but only force user to update on first edit attempt.
Warning: as explained above, this may be considered a hack and usually means that you should focus on making your CRUD logic better, at some point during actual deletion already make a decision what should happen with the related records.
Symfony Form SoftDeleteAware EntityType
I have created a drop-in replacement for EntityType, so in your forms you simple replace EntityType with SoftDeleteAwareEntityType and form will stop breaking, instead placeholder as if there is nothing assigned will be displayed forcing user to update with a new value if the field is required:
NOTE: At the moment of writing, it supports only records with a single int primary key available and it being the association, the foreign key.
Installation process is trivial, require it in your project:
composer require idct/symfony-form-soft-delete-aware-entity-type
As this is not a bundle register in your services (for example services.yaml) file:
If you have autowiring:
IDCT\SymfonyFormSoftDeleteAwareEntityType\SoftDeleteAwareEntityType: ~
Be sure to check lib's README.md.
LINK: ideaconnect/symfony-form-soft-delete-aware-entity-type on github
Created also another extension which adds support for the autocomplete methods provided by symfony/ux
package: ideaconnect/symfony-form-soft-delete-aware-entity-type-ux-autocomplete on github
What the lib does? Quite a simple trick: while we retrieve it simply temporarily disables (suspends) the filter so that doctrine does not fail due to invalid relation constraint, removes the result from options for the EntityType based on the attribute provided by doctrine-extensions and turns soft deletion filter back on.
Everything is of course provided under MIT license, feel free to use if for your own needs, in case of any issues or features suggestions everything is more than welcome. Be sure to know that you are using it at your own risk.