. */ #namespace Doctrine::DBAL::Transactions; /** * Handles transaction savepoint and isolation abstraction * * @author Konsta Vesterinen * @author Lukas Smith (PEAR MDB2 library) * @author Roman Borschel * @license http://www.opensource.org/licenses/lgpl-license.php LGPL * @link www.phpdoctrine.org * @since 1.0 * @version $Revision$ * @deprecated */ class Doctrine_Transaction extends Doctrine_Connection_Module { /** * A transaction is in sleep state when it is not active. */ const STATE_SLEEP = 0; /** * A transaction is in active state when it is active. */ const STATE_ACTIVE = 1; /** * A transaction is in busy state when it is active and has a nesting level > 1. */ const STATE_BUSY = 2; /** * @var integer $_nestingLevel The current nesting level of this transaction. * A nesting level of 0 means there is currently no active * transaction. */ protected $_nestingLevel = 0; /** * @var array $savepoints an array containing all savepoints */ protected $savePoints = array(); /** * Returns the state of this transaction module. * * @see Doctrine_Connection_Transaction::STATE_* constants * @return integer the connection state */ public function getState() { switch ($this->_nestingLevel) { case 0: return self::STATE_SLEEP; break; case 1: return self::STATE_ACTIVE; break; default: return self::STATE_BUSY; } } /** * Gets the current transaction nesting level. * * @return integer * @todo Name suggestion: getNestingLevel(). $transaction->getTransactionLevel() looks odd. */ public function getTransactionLevel() { return $this->_nestingLevel; } /** * Start a transaction or set a savepoint. * * if trying to set a savepoint and there is no active transaction * a new transaction is being started * * This method should only be used by userland-code to initiate transactions. * To initiate a transaction from inside Doctrine use {@link beginInternalTransaction()}. * * Listeners: onPreTransactionBegin, onTransactionBegin * * @param string $savepoint name of a savepoint to set * @throws Doctrine_Transaction_Exception if the transaction fails at database level * @return integer current transaction nesting level */ public function begin($savepoint = null) { $this->conn->connect(); if ( ! is_null($savepoint)) { $this->savePoints[] = $savepoint; $this->createSavePoint($savepoint); } else { if ($this->_nestingLevel == 0) { try { $this->_doBeginTransaction(); } catch (Exception $e) { throw new Doctrine_Transaction_Exception($e->getMessage()); } } } $level = ++$this->_nestingLevel; return $level; } /** * Commits the database changes done during a transaction that is in * progress or release a savepoint. This function may only be called when * auto-committing is disabled, otherwise it will fail. * * Listeners: preTransactionCommit, postTransactionCommit * * @param string $savepoint name of a savepoint to release * @throws Doctrine_Transaction_Exception if the transaction fails at database level * @throws Doctrine_Validator_Exception if the transaction fails due to record validations * @return boolean false if commit couldn't be performed, true otherwise */ public function commit($savepoint = null) { if ($this->_nestingLevel == 0) { throw new Doctrine_Transaction_Exception("Commit failed. There is no active transaction."); } $this->conn->connect(); if ( ! is_null($savepoint)) { $this->_nestingLevel -= $this->removeSavePoints($savepoint); $this->releaseSavePoint($savepoint); } else { if ($this->_nestingLevel == 1) { $this->_doCommit(); } if ($this->_nestingLevel > 0) { $this->_nestingLevel--; } } return true; } /** * Cancel any database changes done during a transaction or since a specific * savepoint that is in progress. This function may only be called when * auto-committing is disabled, otherwise it will fail. Therefore, a new * transaction is implicitly started after canceling the pending changes. * * this method can be listened with onPreTransactionRollback and onTransactionRollback * eventlistener methods * * @param string $savepoint name of a savepoint to rollback to * @throws Doctrine_Transaction_Exception if the rollback operation fails at database level * @return boolean false if rollback couldn't be performed, true otherwise */ public function rollback($savepoint = null) { if ($this->_nestingLevel == 0) { throw new Doctrine_Transaction_Exception("Rollback failed. There is no active transaction."); } $this->conn->connect(); if ($this->_nestingLevel > 1) { $this->_nestingLevel--; return false; } $listener = $this->conn->getAttribute(Doctrine::ATTR_LISTENER); if ( ! is_null($savepoint)) { $this->_nestingLevel -= $this->removeSavePoints($savepoint); $this->rollbackSavePoint($savepoint); } else { $this->_nestingLevel = 0; try { $this->_doRollback(); } catch (Exception $e) { throw new Doctrine_Transaction_Exception($e->getMessage()); } } return true; } /** * Creates a new savepoint. * * @param string $savepoint name of a savepoint to create * @return void */ protected function createSavePoint($savepoint) { throw new Doctrine_Transaction_Exception('Savepoints not supported by this driver.'); } /** * Releases given savepoint. * * @param string $savepoint name of a savepoint to release * @return void */ protected function releaseSavePoint($savepoint) { throw new Doctrine_Transaction_Exception('Savepoints not supported by this driver.'); } /** * Performs a rollback to a specified savepoint. * * @param string $savepoint name of a savepoint to rollback to * @return void */ protected function rollbackSavePoint($savepoint) { throw new Doctrine_Transaction_Exception('Savepoints not supported by this driver.'); } /** * Performs the rollback. */ protected function _doRollback() { $this->conn->getDbh()->rollback(); } /** * Performs the commit. */ protected function _doCommit() { $this->conn->getDbh()->commit(); } /** * Begins a database transaction. */ protected function _doBeginTransaction() { $this->conn->getDbh()->beginTransaction(); } /** * removes a savepoint from the internal savePoints array of this transaction object * and all its children savepoints * * @param sring $savepoint name of the savepoint to remove * @return integer removed savepoints */ private function removeSavePoints($savepoint) { $this->savePoints = array_values($this->savePoints); $found = false; $i = 0; foreach ($this->savePoints as $key => $sp) { if ( ! $found) { if ($sp === $savepoint) { $found = true; } } if ($found) { $i++; unset($this->savePoints[$key]); } } return $i; } /** * Set the transacton isolation level. * (implemented by the connection drivers) * * example: * * * $tx->setIsolation('READ UNCOMMITTED'); * * * @param string standard isolation level * READ UNCOMMITTED (allows dirty reads) * READ COMMITTED (prevents dirty reads) * REPEATABLE READ (prevents nonrepeatable reads) * SERIALIZABLE (prevents phantom reads) * * @throws Doctrine_Transaction_Exception if the feature is not supported by the driver * @throws PDOException if something fails at the PDO level * @return void */ public function setIsolation($isolation) { throw new Doctrine_Transaction_Exception('Transaction isolation levels not supported by this driver.'); } /** * Fetches the current session transaction isolation level. * * note: some drivers may support setting the transaction isolation level * but not fetching it * * @throws Doctrine_Transaction_Exception if the feature is not supported by the driver * @throws PDOException if something fails at the PDO level * @return string returns the current session transaction isolation level */ public function getIsolation() { throw new Doctrine_Transaction_Exception('Fetching transaction isolation level not supported by this driver.'); } }