2010-04-28 6 views
5

J'ai deux modules distincts qui peuvent être utilisés indépendamment, mais Module2 dépend de Module1.Besoin d'aide pour les opérations atomiques imbriquées impliquant des transactions PDO

Le module 2 a une opération qui doit être atomique, et il appelle une opération dans Module1 qui doit également être atomique.

En supposant que j'ai mis PDO :: ATTR_ERRMODE à AOP: ERRMODE_EXCEPTION, le code suivant fortement génériqué et snipped cède ceci: PHP Fatal error: Uncaught exception 'PDOException' message 'Il existe déjà une transaction active'

Module1:

<?php 
class Module1 
{ 
    ... 
    public function atomicOperation($stuff) 
    { 
     $this->pdo->beginTransaction(); 
     try { 
      $stmt = $this->pdo->prepare(...); 
      ... 
      $this->pdo->commit(); 
     } 
     catch (Exception $ex) { 
      $this->pdo->rollBack(); 
      throw $ex; 
     } 
    } 
} 

Module2:

<?php 
class Module2 
{ 
    public $module1; 
    ... 
    public function atomicOperation($stuff) 
    { 
     $this->pdo->beginTransaction(); 
     try { 
      $stmt = $this->pdo->prepare(...); 
      ... 
      $this->module1->atomicOperation($stuff); 
      ... 
      $this->pdo->commit(); 
     } 
     catch (Exception $ex) { 
      $this->pdo->rollBack(); 
      throw $ex; 
     } 
    } 
} 

Je ne suis pas sûr que la meilleure façon d'aller à ce sujet - l'opération imbriquée sera appelée de manière indépendante et absolument doit être être atomique lorsqu'elle est appelée de son propre chef. Placer aux utilisateurs de la classe la responsabilité de gérer la transaction et de préserver l'atomicité n'est pas souhaitable, car je suis certain que les utilisateurs de la classe ne l'appliqueront jamais.

Répondre

4

Vous devez créer votre propre classe qui étend le PDO et gère les transactions. Quelque chose comme:

<?php 
class Db extends PDO{ 
    private $_inTrans = false; 

    public function beginTransaction(){ 
    if(!$this->_inTrans){ 
     $this->_inTrans = parent::beginTransaction(); 
    } 
    return $this->_inTrans; 
    } 

    public function commit(){ 
    if($this->_inTrans){ 
     $this->_inTrans = false; 
     return parent::commit(); 
    } 
    return true; 
    } 

    public function rollBack(){ 
    if($this->_inTrans){ 
     $this->_inTrans = false; 
     return parent::rollBack(); 
    } 
    return true; 
    } 

    public function transactionStarted(){ 
    return $this->_inTrans; 
    } 

} 

Vous devez toujours vérifier toutes les requêtes passées dans le cas où une transaction a commencé là.

Module 1:

<?php 
class Module1 
{ 
    ... 
    public function atomicOperation($stuff) 
    { 
     $transactionAlreadyStarted = $this->pdo->transactionStarted(); 
     if(!$transactionAlreadyStarted){ 
      $this->pdo->beginTransaction(); 
     } 
     try { 
      $stmt = $this->pdo->prepare(...); 
      ... 

      if(!$transactionAlreadyStarted && $this->pdo->transactionStarted()){ 
       $this->pdo->commit(); 
      } 
     } 
     catch (Exception $ex) { 
      if($this->pdo->transactionStarted()){ 
       $this->pdo->rollBack(); 
      } 
      throw $ex; 
     } 
    } 
} 

Module 2:

<?php 
class Module2 
{ 
    public $module1; 
    ... 
    public function atomicOperation($stuff) 
    { 
     $transactionAlreadyStarted = $this->pdo->transactionStarted(); 
     if(!$transactionAlreadyStarted){ 
      $this->pdo->beginTransaction(); 
     } 
     try { 
      $stmt = $this->pdo->prepare(...); 
      ... 
      $this->module1->atomicOperation($stuff); 
      ... 
      if(!$transactionAlreadyStarted && $this->pdo->transactionStarted()){ 
       $this->pdo->commit(); 
      } 
     } 
     catch (Exception $ex) { 
      if($this->pdo->transactionStarted()){ 
       $this->pdo->rollBack(); 
      } 
      throw $ex; 
     } 
    } 
} 
+0

Ajout de quelques peluches. Vos opérations atomiques seront en mesure d'utiliser les transactions seules ou avec des amis autour d'eux. – Arkh

1

solution de Arkh, bien que proche, est mal commettras parce que() et rollback() sont essentiellement couché. Appeler rollback() ou commit() peut renvoyer true lorsque rien ne se passe réellement. En revanche, vous devez utiliser SAVEPOINTs.

Savepoints are supported in some form or other in database systems like PostgreSQL, Oracle, Microsoft SQL Server, MySQL, DB2, SQLite (since 3.6.8), Firebird and Informix (since version 11.50xC3). Savepoints are also defined in the SQL standard.

Dans votre classe DB personnalisée, vous substituez commit, rollback et beginTransaction() et le cas échéant utiliser Savepoints. Vous pouvez aussi essayer d'implémenter inTransaction(), bien que les commit implicites (CREATE TABLE, etc) dans MySQL ne soient pas fiables.

Cette blog post from 2008 a effectivement une implémentation de ce que je dis.

This code will only attempt to use the SAVEPOINT code if you’re using a database driver that supports it

Questions connexes