Transactions
Drupal prend également en charge les transactions, y compris une alternative transparente pour les bases de données qui ne supportent pas les transactions. Cependant, les transactions peuvent devenir assez complexes si vous essayez d’exécuter deux transactions simultanément. Le comportement dans ce cas dépend aussi de la base de données.
Un problème similaire existe avec les verrous imbriqués en C / C ++. Si le code a déjà acquis le verrou A et tente de le reprendre, le code sera bloqué. Si vous écrivez un code qui vérifie s’il possède déjà le verrou et n’essaie pas de le reprendre, vous évitez une situation de blocage, mais vous pouvez libérer le verrou prématurément.
En SQL, nous avons le même problème. Si votre code est déjà dans une transaction, lancer une nouvelle transaction entraîne une conséquence inattendue et fâcheuse : la validation de la transaction en cours et le démarrage d’une nouvelle.
Java résout le problème de l’imbrication de ses verrous en implémentant une prise en charge d’une structure imbriquée, similaire à celle que nous testons ci-dessous. Java permet de marquer les fonctions comme « synchronisées », ce qui force la fonction à attendre l’acquisition du verrou avant de s’exécuter et à libérer le verrou lorsqu’il n’est plus nécessaire. Si une fonction synchronisée en appelle une autre dans la même classe, Java suit l’imbrication des verrous. La fonction externe obtient le verrou, la fonction interne ne réalise pas d’opérations de verrouillage, et la fonction externe libère le verrou lors de son retour.
Bien que nous ne puissions pas déclarer des fonctions « transactionnelles » en PHP, nous pouvons émuler la logique d’imbrication de Java en utilisant des objets avec des constructeurs et des destructeurs. La fonction appelle simplement « $transaction = $connection->startTransaction() » comme première (ou presque première) opération pour se rendre transactionnelle. Si une fonction transactionnelle en appelle une autre, notre niveau d’abstraction des transactions les imbrique sans effectuer d’opérations transactionnelles (du point de vue de la base de données) à l’intérieur des niveaux internes d’imbrication.
Pour démarrer une nouvelle transaction, il suffit d’appeler $transaction = $connection->startTransaction(); dans votre propre code. La transaction restera ouverte tant que la variable $transaction est dans le scope. Quand $transaction est détruite, la transaction sera validée. Si votre transaction est imbriquée dans une autre, Drupal suivra chaque transaction et validera uniquement la transaction la plus externe lorsque le dernier objet transaction sortira du scope, c’est-à-dire que toutes les requêtes correspondantes sont terminées avec succès.
Vous devez assigner la valeur de retour de $connection->startTransaction(); à une variable, comme dans l’exemple. Si vous appelez la méthode sans assigner la valeur de retour à une variable, votre transaction sera validée immédiatement, ce qui la rendra inutile.
Le rollback de la transaction est géré par l’objet de connexion ($connection->rollBack()) dans Drupal 8, mais il est généralement conseillé de l’effectuer via la méthode wrapper de la transaction ($action->rollBack()). La raison pour laquelle cela doit être fait via la méthode rollBack() de la transaction est qu’elle annule cette transaction basée sur son nom, tandis que $connection->rollBack() utilise par défaut le nom drupal_transaction et peut donner des résultats indésirables lorsqu’il est utilisé avec des transactions imbriquées.
Exemple :
function my_transaction_function() { // La transaction s'ouvre ici. $transaction = $connection->startTransaction(); try { $id = $connection->insert('example') ->fields([ 'field1' => 'mystring', 'field2' => 5, ]) ->execute(); my_other_function($id); return $id; } catch (Exception $e) { $transaction->rollBack(); watchdog_exception('my_type', $e); } // Vous pouvez laisser $transaction sortir du scope ici et la transaction // sera automatiquement validée si elle n'a pas déjà été rollbackée. // Cependant, si vous avez plus de travail à faire, vous voudrez valider la transaction // vous-même, comme ceci : $transaction->commit(); // Plus de code ici en dehors de la transaction. } function my_other_function($id) { // La transaction est toujours ouverte ici. if ($id % 2 == 0) { $connection->update('example') ->condition('id', $id) ->fields(['field2' => 10]) ->execute(); } }