Symfony/Doctrine— Imbriquer des transactions SQL

NicolasFz.code
4 min readSep 17, 2022

Le plus souvent Doctrine vous permet de gérer facilement les transactions SQL, les commits et les rollback de vos requêtes. Malheureusement dans certains cas cette gestion peut devenir un peu plus complexe et vous pourriez avoir besoin d’imbriquer des transactions enfants dans des transactions parentes. Mais pas d’inquiétudes, Doctrine sait également très bien gérer ces cas de figures.

Bien que celui-ci puisse être utilisé seul, pour cette article j’utiliserais Doctrine via le Framework Symfony.

L’objectif de cet article sera de créer les transactions imbriquées suivantes :

  1. Une transaction enfant permettant de modifier unitairement un User (et de rollback la modification en cas d’erreur)
  2. Une transaction parente permettant de modifier simultanément tout un groupe d’User (et de rollback simultanément toutes les modifications de tous les Users modifiés en cas d’erreur). Cette transaction parente contiendra plusieurs fois la transaction enfants (une pour chaque User modifié).

Modification unitaire d’une entité

Commençons par créer un Controller de démo qui nous servira dans un premier temps à lister les Users puis à les modifier par la suite.

J’ai crée la classe User qui contient les informations basiques d’une liste d’utilisateurs (Nom, prénom) ainsi qu’un “Group“ qui nous permettra par la suite de les regrouper par sous-ensembles.

Maintenant nous allons créer la transaction enfant qui va nous permettre de modifier un User à la fois.
Pour cela, nous allons définir un service UserManager qui contiendra la méthode updateUserName() qui va nous permettre de modifier un User passé en paramètre.

Le début de la transaction est déclaré via le code suivant :

$this->em->getConnection()->beginTransaction();

(Ici $this->em étant l’entityManager de doctrine)

Ensuite nous pouvons modifier notre User normalement.

Pour terminer, si tout s’est bien passé on valide la transaction avec :

$this->em->getConnection()->commit();

Si un problème est survenu (ici via une Exception), on l’annule et on rollback avec :

$this->em->getConnection()->rollBack();

Bien sûr, dans l’exemple ci-dessus, il est assez peu probable qu’une erreur survienne. Mais vous avez la possibilité de déclarer les cas de rollback que vous désirez.

Cette méthode peu maintenant être testée dans notre Controller de démo.

Modification simultanées de plusieurs entités

Maintenant que la première transaction est réalisée, nous allons réutiliser la méthode updateUserName() dans une nouvelle méthode qui va permettre de modifier plusieurs utilisateurs simultanément.

Pour cette nouvelle méthode nous désirons une nouvelle transaction afin de pouvoir rollback la totalité des modifications en cas d’erreur (et non pas uniquement l’utilisateur sur lequel l’erreur est survenue).

Par défaut, l’imbrication de transaction n’est pas activée dans Doctrine. Pour l’activer, il faut utiliser la ligne suivante :

$this->em->getConnection()->setNestTransactionsWithSavepoints(true);

Mise à part ça, tout le reste se gère comme une transaction simple.
Nous pouvons donc créer facilement une nouvelle méthode qui va pouvoir modifier tout un tableau contenant des d’Users (en utilisant la méthode précédemment créée) et annuler toutes les modifications simultanément en cas d’erreur.

Maintenant nous pouvons ajouter une nouvelle méthode dans notre Controller pour utiliser updateUserNameFromArray().

Et voilà, maintenant nous sommes capables de modifier tout un groupe d’utilisateur en toute sécurité !

Pour information, si dans votre code vous vous perdez un peu dans vos différentes transactions imbriquées ou que vous avez besoin de comportements spécifiques dans une méthode selon si elle est imbriquée ou pas, vous pouvez utiliser la méthode suivante pour retrouver le “niveau d’imbrication” d’une transaction :

$this->em->getConnection()->getTransactionNestingLevel();

Un dernier avertissement

Attention tout de même avec l’imbrication de transaction. Savoir quelle transaction sera ou ne serra pas “commitée” peu vite devenir un casse tête.

De plus même si une transaction est rollback les entités elles ne sont pas remises dans leur état original précédent leur modification. Cela signifie qu’une autre transaction dans la suite de votre code peut terminer de les “commiter” malgré l’erreur précédente.

Vous l’aurez donc compris, l’imbrication de transaction est à utiliser avec parcimonie.

J’espère que cet article sur les transactions Doctrine aura pu vous aider. Si vous avez des remarques ou des suggestions à son sujet, n’hésitez pas à me le faire savoir en commentaire.

--

--

NicolasFz.code

Lead Développeur PHP/Symfony, Chargé de recrutement technique