Si vous êtes arrivé ici, c’est peut-être parce que vous avez ce message :
1. Contexte
Imaginez un Article de blog avec une relation OneToMany vers Picture tout ce qu’il y a de plus classique.
Afin de pouvoir établir un ordre entre ces images nous ajoutons une position à Picture.
Puisque nous sommes malins on ajoute une contrainte d’unicité entre cette position et article_id
2. Exemple
Dans cette configuration, il est possible de reproduire l’erreur.
La condition sinequanone est de procéder à un DELETE et un INSERT dans la même transaction.
Prenons l’exemple suivant :
Bingo. Alors d’où vient le problème concrètement ?
3. Origine
Tout se passe dans l’UnitOfWork (Doctrine\ORM\UnitOfWork) de l’ORM.
Si on regarde d’un peu plus près la méthode commit() on peut constater que les INSERT sont fait avant les DELETE :
4. Solutions
Une première possibilité serait bien sûr d’exécuter les deux actions dans deux transactions différentes.
L’inconvénient avec cette méthode c’est si, pour une raison quelconque, l’ajout de l’image ne se passe pas bien, nous l’avons supprimé sans avoir pu la remplacer. On a donc un problème d’incohérence des données.
Il existe une autre possibilité, c’est de supprimer la contrainte d’unicité.
Pourquoi ? Il est préférable de faire confiance à son domaine plutôt qu’à son schéma de base de données, n’en déplaise à votre esprit de DBA.
Grâce aux options de persistance en cascade et de suppression d’entité orpheline de Doctrine nous pouvons complètement abstraire l’utilisation de l’EntityManager.
De plus, une bonne habitude est d’encapsuler l’accès à vos collections dans des méthodes métier de votre agrégat :
Qu’en pensez-vous ? Quelle solution avez-vous choisie ? Une autre idée ?
N’hésitez pas à m’en faire part sur Twitter !