Aller au contenu

Suppression douce

phil/doctrine-behaviours
TypeGenreRôle
Contract\SoftDeletableinterfacedeletedAt(), isDeleted(), markDeleted(), restore()
SoftDeletableEntitytraitLa colonne deleted_at, datetime_immutable, nullable
SoftDeleteListenerécouteur : onFlushChange une suppression en date de suppression
SoftDeletedFilterfiltre SQL phil_soft_deleteddeleted_at IS NULL sur chaque entité SoftDeletable
$manager->remove($category);
$manager->flush();                              // deleted_at = now; the row stays, the entity stays managed

$manager->getFilters()->disable(SoftDeletedFilter::NAME);
$category->restore();                           // deleted_at = null
$manager->flush();

$manager->remove($category);                    // already deleted: removed for good

Tant qu'il est actif, le filtre cache les lignes supprimées de chaque requête — find(), DQL, les collections d'une relation chargées à la demande. Une clé étrangère ON DELETE CASCADE de la base ne s'exécute jamais, puisqu'aucune ligne n'est supprimée.

Cascades

L'écouteur garde une entité SoftDeletable supprimée en la persistant de nouveau, et cette persistance se propage comme le disent les associations. Pour les entités auxquelles une suppression se propage :

L'enfant estL'association propageL'enfant
SoftDeletablepersist et removeEst supprimé en douceur lui aussi
non SoftDeletablepersist et removeReste : la ligne du parent reste, ses enfants avec elle, prêts pour restore()
non SoftDeletableremove seulementEst supprimé pour de bon

Une application qui veut voir disparaître les enfants avec un parent supprimé en douceur les supprime elle-même, ou mappe l'association avec remove seulement.

Relations vers une ligne supprimée

Une relation vers un seul côté — ManyToOne, OneToOne — vers une ligne supprimée en douceur n'est pas cachée : la clé étrangère garde son identifiant, et charger l'entité liée sous le filtre ne trouve aucune ligne, si bien que Doctrine lève une EntityNotFoundException, comment.article.title pour un article supprimé. Les issues :

  • mettre la clé étrangère à null quand la cible est supprimée en douceur, si la relation est nullable ;
  • lire ces pages avec le filtre désactivé, et montrer la cible comme supprimée avec isDeleted() ;
  • aller chercher la relation par un LEFT JOIN dans la requête : le filtre s'applique à la jointure, et la relation est null.

Une ressource qui devrait quitter sa table, avec ce qu'il faut pour la ramener, va plutôt dans phil/trash-bundle.