From 6705d9b9ccd51fdfc83e4f68856015ea1cd78a27 Mon Sep 17 00:00:00 2001 From: Guilherme Blanco Date: Thu, 29 Apr 2010 22:46:51 -0300 Subject: [PATCH] Introduced the concept of DBAL\Transaction and ORM\EntityTransaction. --- lib/Doctrine/DBAL/Connection.php | 203 ++-------------- lib/Doctrine/DBAL/Driver/Connection.php | 3 - .../DBAL/Driver/OCI8/OCI8Connection.php | 2 +- lib/Doctrine/DBAL/Driver/PDOConnection.php | 2 +- .../DBAL/Driver/PDOMsSql/Connection.php | 2 +- lib/Doctrine/DBAL/Driver/Transaction.php | 42 ++++ .../DBAL/Platforms/AbstractPlatform.php | 13 +- .../DBAL/Platforms/OraclePlatform.php | 8 +- .../DBAL/Platforms/SqlitePlatform.php | 8 +- lib/Doctrine/DBAL/Transaction.php | 229 ++++++++++++++++++ lib/Doctrine/ORM/EntityManager.php | 24 +- lib/Doctrine/ORM/EntityTransaction.php | 154 ++++++++++++ lib/Doctrine/ORM/UnitOfWork.php | 14 +- .../Doctrine/Tests/DBAL/DriverManagerTest.php | 5 +- .../Tests/DBAL/Functional/AllTests.php | 2 +- .../Tests/DBAL/Functional/ConnectionTest.php | 64 ----- .../Tests/DBAL/Functional/TransactionTest.php | 68 ++++++ .../DBAL/Platforms/MsSqlPlatformTest.php | 8 +- .../DBAL/Platforms/MySqlPlatformTest.php | 8 +- .../DBAL/Platforms/OraclePlatformTest.php | 8 +- .../DBAL/Platforms/PostgreSqlPlatformTest.php | 10 +- .../DBAL/Platforms/SqlitePlatformTest.php | 8 +- tests/Doctrine/Tests/Mocks/ConnectionMock.php | 5 +- 23 files changed, 589 insertions(+), 301 deletions(-) create mode 100644 lib/Doctrine/DBAL/Driver/Transaction.php create mode 100644 lib/Doctrine/DBAL/Transaction.php create mode 100644 lib/Doctrine/ORM/EntityTransaction.php delete mode 100644 tests/Doctrine/Tests/DBAL/Functional/ConnectionTest.php create mode 100644 tests/Doctrine/Tests/DBAL/Functional/TransactionTest.php diff --git a/lib/Doctrine/DBAL/Connection.php b/lib/Doctrine/DBAL/Connection.php index 92a3480d9..5c807c697 100644 --- a/lib/Doctrine/DBAL/Connection.php +++ b/lib/Doctrine/DBAL/Connection.php @@ -44,26 +44,6 @@ use PDO, Closure, */ class Connection implements DriverConnection { - /** - * Constant for transaction isolation level READ UNCOMMITTED. - */ - const TRANSACTION_READ_UNCOMMITTED = 1; - - /** - * Constant for transaction isolation level READ COMMITTED. - */ - const TRANSACTION_READ_COMMITTED = 2; - - /** - * Constant for transaction isolation level REPEATABLE READ. - */ - const TRANSACTION_REPEATABLE_READ = 3; - - /** - * Constant for transaction isolation level SERIALIZABLE. - */ - const TRANSACTION_SERIALIZABLE = 4; - /** * The wrapped driver connection. * @@ -88,20 +68,6 @@ class Connection implements DriverConnection */ private $_isConnected = false; - /** - * The transaction nesting level. - * - * @var integer - */ - private $_transactionNestingLevel = 0; - - /** - * The currently active transaction isolation level. - * - * @var integer - */ - private $_transactionIsolationLevel; - /** * The parameters used during creation of the Connection instance. * @@ -130,14 +96,14 @@ class Connection implements DriverConnection * @var Doctrine\DBAL\Driver */ protected $_driver; - - /** - * Flag that indicates whether the current transaction is marked for rollback only. - * - * @var boolean - */ - private $_isRollbackOnly = false; + /** + * The DBAL Transaction. + * + * @var Doctrine\DBAL\Transaction + */ + protected $_transaction; + /** * Initializes a new instance of the Connection class. * @@ -168,6 +134,7 @@ class Connection implements DriverConnection $this->_config = $config; $this->_eventManager = $eventManager; + if ( ! isset($params['platform'])) { $this->_platform = $driver->getDatabasePlatform(); } else if ($params['platform'] instanceof Platforms\AbstractPlatform) { @@ -175,7 +142,8 @@ class Connection implements DriverConnection } else { throw DBALException::invalidPlatformSpecified(); } - $this->_transactionIsolationLevel = $this->_platform->getDefaultTransactionIsolationLevel(); + + $this->_transaction = new Transaction($this); } /** @@ -278,6 +246,16 @@ class Connection implements DriverConnection return $this->_platform; } + /** + * Gets the DBAL Transaction instance. + * + * @return Doctrine\DBAL\Transaction + */ + public function getTransaction() + { + return $this->_transaction; + } + /** * Establishes the connection with the database. * @@ -356,16 +334,6 @@ class Connection implements DriverConnection return $this->_isConnected; } - /** - * Checks whether a transaction is currently active. - * - * @return boolean TRUE if a transaction is currently active, FALSE otherwise. - */ - public function isTransactionActive() - { - return $this->_transactionNestingLevel > 0; - } - /** * Executes an SQL DELETE statement on a table. * @@ -400,28 +368,6 @@ class Connection implements DriverConnection $this->_isConnected = false; } - /** - * Sets the transaction isolation level. - * - * @param integer $level The level to set. - */ - public function setTransactionIsolation($level) - { - $this->_transactionIsolationLevel = $level; - - return $this->executeUpdate($this->_platform->getSetTransactionIsolationSQL($level)); - } - - /** - * Gets the currently active transaction isolation level. - * - * @return integer The current transaction isolation level. - */ - public function getTransactionIsolation() - { - return $this->_transactionIsolationLevel; - } - /** * Executes an SQL UPDATE statement on a table. * @@ -585,10 +531,10 @@ class Connection implements DriverConnection * represents a row of the result set. * @return mixed The projected result of the query. */ - public function project($query, array $params = array(), Closure $function) + public function project($query, array $params, Closure $function) { $result = array(); - $stmt = $this->executeQuery($query, $params); + $stmt = $this->executeQuery($query, $params ?: array()); while ($row = $stmt->fetch()) { $result[] = $function($row); @@ -659,16 +605,6 @@ class Connection implements DriverConnection return $this->_conn->exec($statement); } - /** - * Returns the current transaction nesting level. - * - * @return integer The nesting level. A value of 0 means there's no active transaction. - */ - public function getTransactionNestingLevel() - { - return $this->_transactionNestingLevel; - } - /** * Fetch the SQLSTATE associated with the last database operation. * @@ -708,73 +644,6 @@ class Connection implements DriverConnection return $this->_conn->lastInsertId($seqName); } - /** - * Starts a transaction by suspending auto-commit mode. - * - * @return void - */ - public function beginTransaction() - { - $this->connect(); - - if ($this->_transactionNestingLevel == 0) { - $this->_conn->beginTransaction(); - } - - ++$this->_transactionNestingLevel; - } - - /** - * Commits the current transaction. - * - * @return void - * @throws ConnectionException If the commit failed due to no active transaction or - * because the transaction was marked for rollback only. - */ - public function commit() - { - if ($this->_transactionNestingLevel == 0) { - throw ConnectionException::commitFailedNoActiveTransaction(); - } - if ($this->_isRollbackOnly) { - throw ConnectionException::commitFailedRollbackOnly(); - } - - $this->connect(); - - if ($this->_transactionNestingLevel == 1) { - $this->_conn->commit(); - } - - --$this->_transactionNestingLevel; - } - - /** - * Cancel any database changes done during the current transaction. - * - * this method can be listened with onPreTransactionRollback and onTransactionRollback - * eventlistener methods - * - * @throws ConnectionException If the rollback operation failed. - */ - public function rollback() - { - if ($this->_transactionNestingLevel == 0) { - throw ConnectionException::rollbackFailedNoActiveTransaction(); - } - - $this->connect(); - - if ($this->_transactionNestingLevel == 1) { - $this->_transactionNestingLevel = 0; - $this->_conn->rollback(); - $this->_isRollbackOnly = false; - } else { - $this->_isRollbackOnly = true; - --$this->_transactionNestingLevel; - } - } - /** * Gets the wrapped driver connection. * @@ -802,34 +671,6 @@ class Connection implements DriverConnection return $this->_schemaManager; } - /** - * Marks the current transaction so that the only possible - * outcome for the transaction to be rolled back. - * - * @throws ConnectionException If no transaction is active. - */ - public function setRollbackOnly() - { - if ($this->_transactionNestingLevel == 0) { - throw ConnectionException::noActiveTransaction(); - } - $this->_isRollbackOnly = true; - } - - /** - * Check whether the current transaction is marked for rollback only. - * - * @return boolean - * @throws ConnectionException If no transaction is active. - */ - public function getRollbackOnly() - { - if ($this->_transactionNestingLevel == 0) { - throw ConnectionException::noActiveTransaction(); - } - return $this->_isRollbackOnly; - } - /** * Converts a given value to its database representation according to the conversion * rules of a specific DBAL mapping type. diff --git a/lib/Doctrine/DBAL/Driver/Connection.php b/lib/Doctrine/DBAL/Driver/Connection.php index cee11f31a..9ad19a32f 100644 --- a/lib/Doctrine/DBAL/Driver/Connection.php +++ b/lib/Doctrine/DBAL/Driver/Connection.php @@ -36,9 +36,6 @@ interface Connection function quote($input, $type=\PDO::PARAM_STR); function exec($statement); function lastInsertId($name = null); - function beginTransaction(); - function commit(); - function rollBack(); function errorCode(); function errorInfo(); } \ No newline at end of file diff --git a/lib/Doctrine/DBAL/Driver/OCI8/OCI8Connection.php b/lib/Doctrine/DBAL/Driver/OCI8/OCI8Connection.php index 987bf6c0c..cdaa3cbd7 100644 --- a/lib/Doctrine/DBAL/Driver/OCI8/OCI8Connection.php +++ b/lib/Doctrine/DBAL/Driver/OCI8/OCI8Connection.php @@ -26,7 +26,7 @@ namespace Doctrine\DBAL\Driver\OCI8; * * @since 2.0 */ -class OCI8Connection implements \Doctrine\DBAL\Driver\Connection +class OCI8Connection implements \Doctrine\DBAL\Driver\Connection, \Doctrine\DBAL\Driver\Transaction { private $_dbh; diff --git a/lib/Doctrine/DBAL/Driver/PDOConnection.php b/lib/Doctrine/DBAL/Driver/PDOConnection.php index f0068077e..50d6865b3 100644 --- a/lib/Doctrine/DBAL/Driver/PDOConnection.php +++ b/lib/Doctrine/DBAL/Driver/PDOConnection.php @@ -29,7 +29,7 @@ use \PDO; * * @since 2.0 */ -class PDOConnection extends PDO implements Connection +class PDOConnection extends PDO implements Connection, Transaction { public function __construct($dsn, $user = null, $password = null, array $options = null) { diff --git a/lib/Doctrine/DBAL/Driver/PDOMsSql/Connection.php b/lib/Doctrine/DBAL/Driver/PDOMsSql/Connection.php index 1b5fb4efc..aa8dbd662 100644 --- a/lib/Doctrine/DBAL/Driver/PDOMsSql/Connection.php +++ b/lib/Doctrine/DBAL/Driver/PDOMsSql/Connection.php @@ -26,7 +26,7 @@ namespace Doctrine\DBAL\Driver\PDOMsSql; * * @since 2.0 */ -class Connection extends \PDO implements \Doctrine\DBAL\Driver\Connection +class Connection extends \Doctrine\DBAL\Driver\PDOConnection { /** * Performs the rollback. diff --git a/lib/Doctrine/DBAL/Driver/Transaction.php b/lib/Doctrine/DBAL/Driver/Transaction.php new file mode 100644 index 000000000..318556e2a --- /dev/null +++ b/lib/Doctrine/DBAL/Driver/Transaction.php @@ -0,0 +1,42 @@ +. + */ + +namespace Doctrine\DBAL\Driver; + +/** + * Transaction interface. + * Each Driver Connection must implement this interface. + * + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @link www.doctrine-project.org + * @since 2.0 + * @version $Revision$ + * @author Benjamin Eberlei + * @author Guilherme Blanco + * @author Jonathan Wage + * @author Roman Borschel + */ +interface Transaction +{ + function beginTransaction(); + function commit(); + function rollBack(); +} \ No newline at end of file diff --git a/lib/Doctrine/DBAL/Platforms/AbstractPlatform.php b/lib/Doctrine/DBAL/Platforms/AbstractPlatform.php index 961ca0b7d..88fc848ab 100644 --- a/lib/Doctrine/DBAL/Platforms/AbstractPlatform.php +++ b/lib/Doctrine/DBAL/Platforms/AbstractPlatform.php @@ -23,6 +23,7 @@ namespace Doctrine\DBAL\Platforms; use Doctrine\DBAL\DBALException, Doctrine\DBAL\Connection, + Doctrine\DBAL\Transaction, Doctrine\DBAL\Types, Doctrine\DBAL\Schema\Table, Doctrine\DBAL\Schema\Index, @@ -1445,13 +1446,13 @@ abstract class AbstractPlatform protected function _getTransactionIsolationLevelSQL($level) { switch ($level) { - case Connection::TRANSACTION_READ_UNCOMMITTED: + case Transaction::READ_UNCOMMITTED: return 'READ UNCOMMITTED'; - case Connection::TRANSACTION_READ_COMMITTED: + case Transaction::READ_COMMITTED: return 'READ COMMITTED'; - case Connection::TRANSACTION_REPEATABLE_READ: + case Transaction::REPEATABLE_READ: return 'REPEATABLE READ'; - case Connection::TRANSACTION_SERIALIZABLE: + case Transaction::SERIALIZABLE: return 'SERIALIZABLE'; default: throw new \InvalidArgumentException('Invalid isolation level:' . $level); @@ -1584,11 +1585,11 @@ abstract class AbstractPlatform * Gets the default transaction isolation level of the platform. * * @return integer The default isolation level. - * @see Doctrine\DBAL\Connection\TRANSACTION_* constants. + * @see Doctrine\DBAL\Transaction constants. */ public function getDefaultTransactionIsolationLevel() { - return Connection::TRANSACTION_READ_COMMITTED; + return Transaction::READ_COMMITTED; } /* supports*() metods */ diff --git a/lib/Doctrine/DBAL/Platforms/OraclePlatform.php b/lib/Doctrine/DBAL/Platforms/OraclePlatform.php index b391045c8..74593edf1 100644 --- a/lib/Doctrine/DBAL/Platforms/OraclePlatform.php +++ b/lib/Doctrine/DBAL/Platforms/OraclePlatform.php @@ -146,12 +146,12 @@ class OraclePlatform extends AbstractPlatform protected function _getTransactionIsolationLevelSQL($level) { switch ($level) { - case \Doctrine\DBAL\Connection::TRANSACTION_READ_UNCOMMITTED: + case \Doctrine\DBAL\Transaction::READ_UNCOMMITTED: return 'READ UNCOMMITTED'; - case \Doctrine\DBAL\Connection::TRANSACTION_READ_COMMITTED: + case \Doctrine\DBAL\Transaction::READ_COMMITTED: return 'READ COMMITTED'; - case \Doctrine\DBAL\Connection::TRANSACTION_REPEATABLE_READ: - case \Doctrine\DBAL\Connection::TRANSACTION_SERIALIZABLE: + case \Doctrine\DBAL\Transaction::REPEATABLE_READ: + case \Doctrine\DBAL\Transaction::SERIALIZABLE: return 'SERIALIZABLE'; default: return parent::_getTransactionIsolationLevelSQL($level); diff --git a/lib/Doctrine/DBAL/Platforms/SqlitePlatform.php b/lib/Doctrine/DBAL/Platforms/SqlitePlatform.php index a1c2184b9..022b819d0 100644 --- a/lib/Doctrine/DBAL/Platforms/SqlitePlatform.php +++ b/lib/Doctrine/DBAL/Platforms/SqlitePlatform.php @@ -130,11 +130,11 @@ class SqlitePlatform extends AbstractPlatform protected function _getTransactionIsolationLevelSQL($level) { switch ($level) { - case \Doctrine\DBAL\Connection::TRANSACTION_READ_UNCOMMITTED: + case \Doctrine\DBAL\Transaction::READ_UNCOMMITTED: return 0; - case \Doctrine\DBAL\Connection::TRANSACTION_READ_COMMITTED: - case \Doctrine\DBAL\Connection::TRANSACTION_REPEATABLE_READ: - case \Doctrine\DBAL\Connection::TRANSACTION_SERIALIZABLE: + case \Doctrine\DBAL\Transaction::READ_COMMITTED: + case \Doctrine\DBAL\Transaction::REPEATABLE_READ: + case \Doctrine\DBAL\Transaction::SERIALIZABLE: return 1; default: return parent::_getTransactionIsolationLevelSQL($level); diff --git a/lib/Doctrine/DBAL/Transaction.php b/lib/Doctrine/DBAL/Transaction.php new file mode 100644 index 000000000..3b29f3c79 --- /dev/null +++ b/lib/Doctrine/DBAL/Transaction.php @@ -0,0 +1,229 @@ +. + */ + +namespace Doctrine\DBAL; + +/** + * The Transaction class is the central access point to DBAL Transaction functionality. + * + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @link www.doctrine-project.org + * @since 2.0 + * @version $Revision$ + * @author Benjamin Eberlei + * @author Guilherme Blanco + * @author Jonathan Wage + * @author Roman Borschel + */ +class Transaction +{ + /** + * Constant for transaction isolation level READ UNCOMMITTED. + */ + const READ_UNCOMMITTED = 1; + + /** + * Constant for transaction isolation level READ COMMITTED. + */ + const READ_COMMITTED = 2; + + /** + * Constant for transaction isolation level REPEATABLE READ. + */ + const REPEATABLE_READ = 3; + + /** + * Constant for transaction isolation level SERIALIZABLE. + */ + const SERIALIZABLE = 4; + + /** + * The transaction nesting level. + * + * @var integer + */ + private $_transactionNestingLevel = 0; + + /** + * The currently active transaction isolation level. + * + * @var integer + */ + private $_transactionIsolationLevel; + + /** + * Flag that indicates whether the current transaction is marked for rollback only. + * + * @var boolean + */ + private $_isRollbackOnly = false; + + /** + * Constructor + * + * @param Connection $conn The DBAL Connection + */ + public function __construct(Connection $conn) + { + $this->_conn = $conn; + + $this->_transactionIsolationLevel = $conn->getDatabasePlatform()->getDefaultTransactionIsolationLevel(); + } + + /** + * Checks whether a transaction is currently active. + * + * @return boolean TRUE if a transaction is currently active, FALSE otherwise. + */ + public function isTransactionActive() + { + return $this->_transactionNestingLevel > 0; + } + + /** + * Sets the transaction isolation level. + * + * @param integer $level The level to set. + */ + public function setTransactionIsolation($level) + { + $this->_transactionIsolationLevel = $level; + + return $this->executeUpdate($this->_conn->getDatabasePlatform()->getSetTransactionIsolationSQL($level)); + } + + /** + * Gets the currently active transaction isolation level. + * + * @return integer The current transaction isolation level. + */ + public function getTransactionIsolation() + { + return $this->_transactionIsolationLevel; + } + + /** + * Returns the current transaction nesting level. + * + * @return integer The nesting level. A value of 0 means there's no active transaction. + */ + public function getTransactionNestingLevel() + { + return $this->_transactionNestingLevel; + } + + /** + * Starts a transaction by suspending auto-commit mode. + * + * @return void + */ + public function begin() + { + $conn = $this->_conn->getWrappedConnection(); + + if ($this->_transactionNestingLevel == 0) { + $conn->beginTransaction(); + } + + ++$this->_transactionNestingLevel; + } + + /** + * Commits the current transaction. + * + * @return void + * @throws ConnectionException If the commit failed due to no active transaction or + * because the transaction was marked for rollback only. + */ + public function commit() + { + if ($this->_transactionNestingLevel == 0) { + throw ConnectionException::commitFailedNoActiveTransaction(); + } + + if ($this->_isRollbackOnly) { + throw ConnectionException::commitFailedRollbackOnly(); + } + + $conn = $this->_conn->getWrappedConnection(); + + if ($this->_transactionNestingLevel == 1) { + $conn->commit(); + } + + --$this->_transactionNestingLevel; + } + + /** + * Cancel any database changes done during the current transaction. + * + * this method can be listened with onPreTransactionRollback and onTransactionRollback + * eventlistener methods + * + * @throws ConnectionException If the rollback operation failed. + */ + public function rollback() + { + if ($this->_transactionNestingLevel == 0) { + throw ConnectionException::rollbackFailedNoActiveTransaction(); + } + + if ($this->_transactionNestingLevel == 1) { + $this->_transactionNestingLevel = 0; + $this->_conn->getWrappedConnection()->rollback(); + $this->_isRollbackOnly = false; + } else { + $this->_isRollbackOnly = true; + --$this->_transactionNestingLevel; + } + } + + /** + * Marks the current transaction so that the only possible + * outcome for the transaction to be rolled back. + * + * @throws ConnectionException If no transaction is active. + */ + public function setRollbackOnly() + { + if ($this->_transactionNestingLevel == 0) { + throw ConnectionException::noActiveTransaction(); + } + + $this->_isRollbackOnly = true; + } + + /** + * Check whether the current transaction is marked for rollback only. + * + * @return boolean + * @throws ConnectionException If no transaction is active. + */ + public function getRollbackOnly() + { + if ($this->_transactionNestingLevel == 0) { + throw ConnectionException::noActiveTransaction(); + } + + return $this->_isRollbackOnly; + } +} \ No newline at end of file diff --git a/lib/Doctrine/ORM/EntityManager.php b/lib/Doctrine/ORM/EntityManager.php index c4aa9bb55..843682ab0 100644 --- a/lib/Doctrine/ORM/EntityManager.php +++ b/lib/Doctrine/ORM/EntityManager.php @@ -107,6 +107,13 @@ class EntityManager */ private $_closed = false; + /** + * The ORM Entity Transaction. + * + * @var Doctrine\ORM\EntityTransaction + */ + protected $_transaction; + /** * Creates a new EntityManager that operates on the given database connection * and uses the given Configuration and EventManager implementations. @@ -127,6 +134,7 @@ class EntityManager $config->getProxyDir(), $config->getProxyNamespace(), $config->getAutoGenerateProxyClasses()); + $this->_transaction = new EntityTransaction($conn->getTransaction()); } /** @@ -149,6 +157,16 @@ class EntityManager return $this->_metadataFactory; } + /** + * Gets the ORM Transaction instance. + * + * @return Doctrine\ORM\EntityTransaction + */ + public function getTransaction() + { + return $this->_transaction; + } + /** * Gets an ExpressionBuilder used for object-oriented construction of query expressions. * @@ -175,7 +193,7 @@ class EntityManager */ public function beginTransaction() { - $this->_conn->beginTransaction(); + $this->getTransaction()->begin(); } /** @@ -183,7 +201,7 @@ class EntityManager */ public function commit() { - $this->_conn->commit(); + $this->getTransaction()->commit(); } /** @@ -192,7 +210,7 @@ class EntityManager */ public function rollback() { - $this->_conn->rollback(); + $this->getTransaction()->rollback(); $this->close(); } diff --git a/lib/Doctrine/ORM/EntityTransaction.php b/lib/Doctrine/ORM/EntityTransaction.php new file mode 100644 index 000000000..80695ee20 --- /dev/null +++ b/lib/Doctrine/ORM/EntityTransaction.php @@ -0,0 +1,154 @@ +. + */ + +namespace Doctrine\ORM; + +use Doctrine\DBAL\Transaction; + +/** + * The Transaction class is the central access point to ORM Transaction functionality. + * This class acts more as a delegate class to the DBAL Transaction functionality. + * + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @link www.doctrine-project.org + * @since 2.0 + * @version $Revision$ + * @author Benjamin Eberlei + * @author Guilherme Blanco + * @author Jonathan Wage + * @author Roman Borschel + */ +class EntityTransaction +{ + /** + * The wrapped DBAL Transaction. + * + * @var Doctrine\DBAL\Transaction + */ + protected $_wrappedTransaction; + + /** + * Constructor. + * + * @param Transaction $transaction + */ + public function __construct(Transaction $transaction) + { + $this->_wrappedTransaction = $transaction; + } + + /** + * Checks whether a transaction is currently active. + * + * @return boolean TRUE if a transaction is currently active, FALSE otherwise. + */ + public function isTransactionActive() + { + return $this->_wrappedTransaction->isTransactionActive(); + } + + /** + * Sets the transaction isolation level. + * + * @param integer $level The level to set. + */ + public function setTransactionIsolation($level) + { + return $this->_wrappedTransaction->setTransactionIsolation($level); + } + + /** + * Gets the currently active transaction isolation level. + * + * @return integer The current transaction isolation level. + */ + public function getTransactionIsolation() + { + return $this->_wrappedTransaction->getTransactionIsolation(); + } + + /** + * Returns the current transaction nesting level. + * + * @return integer The nesting level. A value of 0 means there's no active transaction. + */ + public function getTransactionNestingLevel() + { + return $this->_wrappedTransaction->getTransactionNestingLevel(); + } + + /** + * Starts a transaction by suspending auto-commit mode. + * + * @return void + */ + public function begin() + { + $this->_wrappedTransaction->begin(); + } + + /** + * Commits the current transaction. + * + * @return void + * @throws Doctrine\DBAL\ConnectionException If the commit failed due to no active transaction or + * because the transaction was marked for rollback only. + */ + public function commit() + { + $this->_wrappedTransaction->commit(); + } + + /** + * Cancel any database changes done during the current transaction. + * + * this method can be listened with onPreTransactionRollback and onTransactionRollback + * eventlistener methods + * + * @throws Doctrine\DBAL\ConnectionException If the rollback operation failed. + */ + public function rollback() + { + $this->_wrappedTransaction->rollback(); + } + + /** + * Marks the current transaction so that the only possible + * outcome for the transaction to be rolled back. + * + * @throws ConnectionException If no transaction is active. + */ + public function setRollbackOnly() + { + $this->_wrappedTransaction->setRollbackOnly(); + } + + /** + * Check whether the current transaction is marked for rollback only. + * + * @return boolean + * @throws ConnectionException If no transaction is active. + */ + public function getRollbackOnly() + { + return $this->_wrappedTransaction->getRollbackOnly(); + } +} \ No newline at end of file diff --git a/lib/Doctrine/ORM/UnitOfWork.php b/lib/Doctrine/ORM/UnitOfWork.php index 46628c904..96cbcc99c 100644 --- a/lib/Doctrine/ORM/UnitOfWork.php +++ b/lib/Doctrine/ORM/UnitOfWork.php @@ -281,11 +281,12 @@ class UnitOfWork implements PropertyChangedListener // Now we need a commit order to maintain referential integrity $commitOrder = $this->_getCommitOrder(); + + $tx = $this->_em->getTransaction(); - $conn = $this->_em->getConnection(); + try { + $tx->begin(); - $conn->beginTransaction(); - try { if ($this->_entityInsertions) { foreach ($commitOrder as $class) { $this->_executeInserts($class); @@ -321,11 +322,10 @@ class UnitOfWork implements PropertyChangedListener } } - $conn->commit(); + $tx->commit(); } catch (\Exception $e) { - $conn->setRollbackOnly(); - $conn->rollback(); - $this->_em->close(); + $tx->rollback(); + throw $e; } diff --git a/tests/Doctrine/Tests/DBAL/DriverManagerTest.php b/tests/Doctrine/Tests/DBAL/DriverManagerTest.php index d7d0fb94f..a95c9ad67 100644 --- a/tests/Doctrine/Tests/DBAL/DriverManagerTest.php +++ b/tests/Doctrine/Tests/DBAL/DriverManagerTest.php @@ -56,12 +56,11 @@ class DriverManagerTest extends \Doctrine\Tests\DbalTestCase public function testCustomWrapper() { - $wrapperMock = $this->getMock('\Doctrine\DBAL\Connection', array(), array(), '', false); - $wrapperClass = get_class($wrapperMock); + $wrapperClass = 'Doctrine\Tests\Mocks\ConnectionMock'; $options = array( 'pdo' => new \PDO('sqlite::memory:'), - 'wrapperClass' => $wrapperClass + 'wrapperClass' => $wrapperClass, ); $conn = \Doctrine\DBAL\DriverManager::getConnection($options); diff --git a/tests/Doctrine/Tests/DBAL/Functional/AllTests.php b/tests/Doctrine/Tests/DBAL/Functional/AllTests.php index 31719886b..50825be9e 100644 --- a/tests/Doctrine/Tests/DBAL/Functional/AllTests.php +++ b/tests/Doctrine/Tests/DBAL/Functional/AllTests.php @@ -25,7 +25,7 @@ class AllTests $suite->addTestSuite('Doctrine\Tests\DBAL\Functional\Schema\MySqlSchemaManagerTest'); $suite->addTestSuite('Doctrine\Tests\DBAL\Functional\Schema\PostgreSqlSchemaManagerTest'); $suite->addTestSuite('Doctrine\Tests\DBAL\Functional\Schema\OracleSchemaManagerTest'); - $suite->addTestSuite('Doctrine\Tests\DBAL\Functional\ConnectionTest'); + $suite->addTestSuite('Doctrine\Tests\DBAL\Functional\TransactionTest'); return $suite; } diff --git a/tests/Doctrine/Tests/DBAL/Functional/ConnectionTest.php b/tests/Doctrine/Tests/DBAL/Functional/ConnectionTest.php deleted file mode 100644 index 8ed7a57d4..000000000 --- a/tests/Doctrine/Tests/DBAL/Functional/ConnectionTest.php +++ /dev/null @@ -1,64 +0,0 @@ -_conn->beginTransaction(); - $this->assertEquals(1, $this->_conn->getTransactionNestingLevel()); - - try { - $this->_conn->beginTransaction(); - $this->assertEquals(2, $this->_conn->getTransactionNestingLevel()); - throw new \Exception; - $this->_conn->commit(); // never reached - } catch (\Exception $e) { - $this->_conn->rollback(); - $this->assertEquals(1, $this->_conn->getTransactionNestingLevel()); - //no rethrow - } - $this->assertTrue($this->_conn->getRollbackOnly()); - - $this->_conn->commit(); // should throw exception - $this->fail('Transaction commit after failed nested transaction should fail.'); - } catch (ConnectionException $e) { - $this->assertEquals(1, $this->_conn->getTransactionNestingLevel()); - $this->_conn->rollback(); - $this->assertEquals(0, $this->_conn->getTransactionNestingLevel()); - } - } - - public function testTransactionBehavior() - { - try { - $this->_conn->beginTransaction(); - $this->assertEquals(1, $this->_conn->getTransactionNestingLevel()); - - throw new \Exception; - - $this->_conn->commit(); // never reached - } catch (\Exception $e) { - $this->assertEquals(1, $this->_conn->getTransactionNestingLevel()); - $this->_conn->rollback(); - $this->assertEquals(0, $this->_conn->getTransactionNestingLevel()); - } - - try { - $this->_conn->beginTransaction(); - $this->assertEquals(1, $this->_conn->getTransactionNestingLevel()); - $this->_conn->commit(); - } catch (\Exception $e) { - $this->_conn->rollback(); - $this->assertEquals(0, $this->_conn->getTransactionNestingLevel()); - } - } - -} \ No newline at end of file diff --git a/tests/Doctrine/Tests/DBAL/Functional/TransactionTest.php b/tests/Doctrine/Tests/DBAL/Functional/TransactionTest.php new file mode 100644 index 000000000..3663bbaaf --- /dev/null +++ b/tests/Doctrine/Tests/DBAL/Functional/TransactionTest.php @@ -0,0 +1,68 @@ +_conn->getTransaction(); + + try { + $tx->begin(); + $this->assertEquals(1, $tx->getTransactionNestingLevel()); + + try { + $tx->begin(); + $this->assertEquals(2, $tx->getTransactionNestingLevel()); + throw new \Exception; + $tx->commit(); // never reached + } catch (\Exception $e) { + $tx->rollback(); + $this->assertEquals(1, $tx->getTransactionNestingLevel()); + //no rethrow + } + $this->assertTrue($tx->getRollbackOnly()); + + $tx->commit(); // should throw exception + $this->fail('Transaction commit after failed nested transaction should fail.'); + } catch (ConnectionException $e) { + $this->assertEquals(1, $tx->getTransactionNestingLevel()); + $tx->rollback(); + $this->assertEquals(0, $tx->getTransactionNestingLevel()); + } + } + + public function testTransactionBehavior() + { + $tx = $this->_conn->getTransaction(); + + try { + $tx->begin(); + $this->assertEquals(1, $tx->getTransactionNestingLevel()); + + throw new \Exception; + + $tx->commit(); // never reached + } catch (\Exception $e) { + $this->assertEquals(1, $tx->getTransactionNestingLevel()); + $tx->rollback(); + $this->assertEquals(0, $tx->getTransactionNestingLevel()); + } + + try { + $tx->begin(); + $this->assertEquals(1, $tx->getTransactionNestingLevel()); + $tx->commit(); + } catch (\Exception $e) { + $tx->rollback(); + $this->assertEquals(0, $tx->getTransactionNestingLevel()); + } + } + +} \ No newline at end of file diff --git a/tests/Doctrine/Tests/DBAL/Platforms/MsSqlPlatformTest.php b/tests/Doctrine/Tests/DBAL/Platforms/MsSqlPlatformTest.php index 1302be035..e0691d74b 100644 --- a/tests/Doctrine/Tests/DBAL/Platforms/MsSqlPlatformTest.php +++ b/tests/Doctrine/Tests/DBAL/Platforms/MsSqlPlatformTest.php @@ -44,19 +44,19 @@ class MsSqlPlatformTest extends AbstractPlatformTestCase { $this->assertEquals( 'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED', - $this->_platform->getSetTransactionIsolationSQL(\Doctrine\DBAL\Connection::TRANSACTION_READ_UNCOMMITTED) + $this->_platform->getSetTransactionIsolationSQL(\Doctrine\DBAL\Transaction::READ_UNCOMMITTED) ); $this->assertEquals( 'SET TRANSACTION ISOLATION LEVEL READ COMMITTED', - $this->_platform->getSetTransactionIsolationSQL(\Doctrine\DBAL\Connection::TRANSACTION_READ_COMMITTED) + $this->_platform->getSetTransactionIsolationSQL(\Doctrine\DBAL\Transaction::READ_COMMITTED) ); $this->assertEquals( 'SET TRANSACTION ISOLATION LEVEL REPEATABLE READ', - $this->_platform->getSetTransactionIsolationSQL(\Doctrine\DBAL\Connection::TRANSACTION_REPEATABLE_READ) + $this->_platform->getSetTransactionIsolationSQL(\Doctrine\DBAL\Transaction::REPEATABLE_READ) ); $this->assertEquals( 'SET TRANSACTION ISOLATION LEVEL SERIALIZABLE', - $this->_platform->getSetTransactionIsolationSQL(\Doctrine\DBAL\Connection::TRANSACTION_SERIALIZABLE) + $this->_platform->getSetTransactionIsolationSQL(\Doctrine\DBAL\Transaction::SERIALIZABLE) ); } diff --git a/tests/Doctrine/Tests/DBAL/Platforms/MySqlPlatformTest.php b/tests/Doctrine/Tests/DBAL/Platforms/MySqlPlatformTest.php index 8dbeac5db..b2c916aec 100644 --- a/tests/Doctrine/Tests/DBAL/Platforms/MySqlPlatformTest.php +++ b/tests/Doctrine/Tests/DBAL/Platforms/MySqlPlatformTest.php @@ -53,20 +53,20 @@ class MySqlPlatformTest extends AbstractPlatformTestCase { $this->assertEquals( 'SET SESSION TRANSACTION ISOLATION LEVEL READ UNCOMMITTED', - $this->_platform->getSetTransactionIsolationSQL(\Doctrine\DBAL\Connection::TRANSACTION_READ_UNCOMMITTED), + $this->_platform->getSetTransactionIsolationSQL(\Doctrine\DBAL\Transaction::READ_UNCOMMITTED), '' ); $this->assertEquals( 'SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED', - $this->_platform->getSetTransactionIsolationSQL(\Doctrine\DBAL\Connection::TRANSACTION_READ_COMMITTED) + $this->_platform->getSetTransactionIsolationSQL(\Doctrine\DBAL\Transaction::READ_COMMITTED) ); $this->assertEquals( 'SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ', - $this->_platform->getSetTransactionIsolationSQL(\Doctrine\DBAL\Connection::TRANSACTION_REPEATABLE_READ) + $this->_platform->getSetTransactionIsolationSQL(\Doctrine\DBAL\Transaction::REPEATABLE_READ) ); $this->assertEquals( 'SET SESSION TRANSACTION ISOLATION LEVEL SERIALIZABLE', - $this->_platform->getSetTransactionIsolationSQL(\Doctrine\DBAL\Connection::TRANSACTION_SERIALIZABLE) + $this->_platform->getSetTransactionIsolationSQL(\Doctrine\DBAL\Transaction::SERIALIZABLE) ); } diff --git a/tests/Doctrine/Tests/DBAL/Platforms/OraclePlatformTest.php b/tests/Doctrine/Tests/DBAL/Platforms/OraclePlatformTest.php index 8fb24abf6..c2b02d50f 100644 --- a/tests/Doctrine/Tests/DBAL/Platforms/OraclePlatformTest.php +++ b/tests/Doctrine/Tests/DBAL/Platforms/OraclePlatformTest.php @@ -55,19 +55,19 @@ class OraclePlatformTest extends AbstractPlatformTestCase { $this->assertEquals( 'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED', - $this->_platform->getSetTransactionIsolationSQL(\Doctrine\DBAL\Connection::TRANSACTION_READ_UNCOMMITTED) + $this->_platform->getSetTransactionIsolationSQL(\Doctrine\DBAL\Transaction::READ_UNCOMMITTED) ); $this->assertEquals( 'SET TRANSACTION ISOLATION LEVEL READ COMMITTED', - $this->_platform->getSetTransactionIsolationSQL(\Doctrine\DBAL\Connection::TRANSACTION_READ_COMMITTED) + $this->_platform->getSetTransactionIsolationSQL(\Doctrine\DBAL\Transaction::READ_COMMITTED) ); $this->assertEquals( 'SET TRANSACTION ISOLATION LEVEL SERIALIZABLE', - $this->_platform->getSetTransactionIsolationSQL(\Doctrine\DBAL\Connection::TRANSACTION_REPEATABLE_READ) + $this->_platform->getSetTransactionIsolationSQL(\Doctrine\DBAL\Transaction::REPEATABLE_READ) ); $this->assertEquals( 'SET TRANSACTION ISOLATION LEVEL SERIALIZABLE', - $this->_platform->getSetTransactionIsolationSQL(\Doctrine\DBAL\Connection::TRANSACTION_SERIALIZABLE) + $this->_platform->getSetTransactionIsolationSQL(\Doctrine\DBAL\Transaction::SERIALIZABLE) ); } diff --git a/tests/Doctrine/Tests/DBAL/Platforms/PostgreSqlPlatformTest.php b/tests/Doctrine/Tests/DBAL/Platforms/PostgreSqlPlatformTest.php index 92596b021..75a8aabe8 100644 --- a/tests/Doctrine/Tests/DBAL/Platforms/PostgreSqlPlatformTest.php +++ b/tests/Doctrine/Tests/DBAL/Platforms/PostgreSqlPlatformTest.php @@ -4,7 +4,7 @@ namespace Doctrine\Tests\DBAL\Platforms; use Doctrine\DBAL\Platforms\PostgreSqlPlatform; use Doctrine\DBAL\Types\Type; -use Doctrine\DBAL\Connection; +use Doctrine\DBAL\Transaction; require_once __DIR__ . '/../../TestInit.php'; @@ -74,19 +74,19 @@ class PostgreSqlPlatformTest extends AbstractPlatformTestCase { $this->assertEquals( 'SET SESSION CHARACTERISTICS AS TRANSACTION ISOLATION LEVEL READ UNCOMMITTED', - $this->_platform->getSetTransactionIsolationSQL(Connection::TRANSACTION_READ_UNCOMMITTED) + $this->_platform->getSetTransactionIsolationSQL(Transaction::READ_UNCOMMITTED) ); $this->assertEquals( 'SET SESSION CHARACTERISTICS AS TRANSACTION ISOLATION LEVEL READ COMMITTED', - $this->_platform->getSetTransactionIsolationSQL(Connection::TRANSACTION_READ_COMMITTED) + $this->_platform->getSetTransactionIsolationSQL(Transaction::READ_COMMITTED) ); $this->assertEquals( 'SET SESSION CHARACTERISTICS AS TRANSACTION ISOLATION LEVEL REPEATABLE READ', - $this->_platform->getSetTransactionIsolationSQL(Connection::TRANSACTION_REPEATABLE_READ) + $this->_platform->getSetTransactionIsolationSQL(Transaction::REPEATABLE_READ) ); $this->assertEquals( 'SET SESSION CHARACTERISTICS AS TRANSACTION ISOLATION LEVEL SERIALIZABLE', - $this->_platform->getSetTransactionIsolationSQL(Connection::TRANSACTION_SERIALIZABLE) + $this->_platform->getSetTransactionIsolationSQL(Transaction::SERIALIZABLE) ); } diff --git a/tests/Doctrine/Tests/DBAL/Platforms/SqlitePlatformTest.php b/tests/Doctrine/Tests/DBAL/Platforms/SqlitePlatformTest.php index 4d09a2e0d..5a56b72bc 100644 --- a/tests/Doctrine/Tests/DBAL/Platforms/SqlitePlatformTest.php +++ b/tests/Doctrine/Tests/DBAL/Platforms/SqlitePlatformTest.php @@ -36,10 +36,10 @@ class SqlitePlatformTest extends AbstractPlatformTestCase public function testGeneratesTransactionCommands() { - $this->assertEquals('PRAGMA read_uncommitted = 0', $this->_platform->getSetTransactionIsolationSQL(\Doctrine\DBAL\Connection::TRANSACTION_READ_UNCOMMITTED)); - $this->assertEquals('PRAGMA read_uncommitted = 1', $this->_platform->getSetTransactionIsolationSQL(\Doctrine\DBAL\Connection::TRANSACTION_READ_COMMITTED)); - $this->assertEquals('PRAGMA read_uncommitted = 1', $this->_platform->getSetTransactionIsolationSQL(\Doctrine\DBAL\Connection::TRANSACTION_REPEATABLE_READ)); - $this->assertEquals('PRAGMA read_uncommitted = 1', $this->_platform->getSetTransactionIsolationSQL(\Doctrine\DBAL\Connection::TRANSACTION_SERIALIZABLE)); + $this->assertEquals('PRAGMA read_uncommitted = 0', $this->_platform->getSetTransactionIsolationSQL(\Doctrine\DBAL\Transaction::READ_UNCOMMITTED)); + $this->assertEquals('PRAGMA read_uncommitted = 1', $this->_platform->getSetTransactionIsolationSQL(\Doctrine\DBAL\Transaction::READ_COMMITTED)); + $this->assertEquals('PRAGMA read_uncommitted = 1', $this->_platform->getSetTransactionIsolationSQL(\Doctrine\DBAL\Transaction::REPEATABLE_READ)); + $this->assertEquals('PRAGMA read_uncommitted = 1', $this->_platform->getSetTransactionIsolationSQL(\Doctrine\DBAL\Transaction::SERIALIZABLE)); } public function testPrefersIdentityColumns() diff --git a/tests/Doctrine/Tests/Mocks/ConnectionMock.php b/tests/Doctrine/Tests/Mocks/ConnectionMock.php index 41f5ebab0..fabecf87a 100644 --- a/tests/Doctrine/Tests/Mocks/ConnectionMock.php +++ b/tests/Doctrine/Tests/Mocks/ConnectionMock.php @@ -11,8 +11,11 @@ class ConnectionMock extends \Doctrine\DBAL\Connection public function __construct(array $params, $driver, $config = null, $eventManager = null) { - parent::__construct($params, $driver, $config, $eventManager); $this->_platformMock = new DatabasePlatformMock(); + + parent::__construct($params, $driver, $config, $eventManager); + + // Override possible assignment of platform to database platform mock $this->_platform = $this->_platformMock; }