1
0
mirror of synced 2025-01-18 14:31:40 +03:00

Introduced the concept of DBAL\Transaction and ORM\EntityTransaction.

This commit is contained in:
Guilherme Blanco 2010-04-29 22:46:51 -03:00
parent 50f3435e84
commit 6705d9b9cc
23 changed files with 589 additions and 301 deletions

View File

@ -44,26 +44,6 @@ use PDO, Closure,
*/ */
class Connection implements DriverConnection 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. * The wrapped driver connection.
* *
@ -88,20 +68,6 @@ class Connection implements DriverConnection
*/ */
private $_isConnected = false; 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. * The parameters used during creation of the Connection instance.
* *
@ -130,14 +96,14 @@ class Connection implements DriverConnection
* @var Doctrine\DBAL\Driver * @var Doctrine\DBAL\Driver
*/ */
protected $_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. * Initializes a new instance of the Connection class.
* *
@ -168,6 +134,7 @@ class Connection implements DriverConnection
$this->_config = $config; $this->_config = $config;
$this->_eventManager = $eventManager; $this->_eventManager = $eventManager;
if ( ! isset($params['platform'])) { if ( ! isset($params['platform'])) {
$this->_platform = $driver->getDatabasePlatform(); $this->_platform = $driver->getDatabasePlatform();
} else if ($params['platform'] instanceof Platforms\AbstractPlatform) { } else if ($params['platform'] instanceof Platforms\AbstractPlatform) {
@ -175,7 +142,8 @@ class Connection implements DriverConnection
} else { } else {
throw DBALException::invalidPlatformSpecified(); throw DBALException::invalidPlatformSpecified();
} }
$this->_transactionIsolationLevel = $this->_platform->getDefaultTransactionIsolationLevel();
$this->_transaction = new Transaction($this);
} }
/** /**
@ -278,6 +246,16 @@ class Connection implements DriverConnection
return $this->_platform; return $this->_platform;
} }
/**
* Gets the DBAL Transaction instance.
*
* @return Doctrine\DBAL\Transaction
*/
public function getTransaction()
{
return $this->_transaction;
}
/** /**
* Establishes the connection with the database. * Establishes the connection with the database.
* *
@ -356,16 +334,6 @@ class Connection implements DriverConnection
return $this->_isConnected; 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. * Executes an SQL DELETE statement on a table.
* *
@ -400,28 +368,6 @@ class Connection implements DriverConnection
$this->_isConnected = false; $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. * Executes an SQL UPDATE statement on a table.
* *
@ -585,10 +531,10 @@ class Connection implements DriverConnection
* represents a row of the result set. * represents a row of the result set.
* @return mixed The projected result of the query. * @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(); $result = array();
$stmt = $this->executeQuery($query, $params); $stmt = $this->executeQuery($query, $params ?: array());
while ($row = $stmt->fetch()) { while ($row = $stmt->fetch()) {
$result[] = $function($row); $result[] = $function($row);
@ -659,16 +605,6 @@ class Connection implements DriverConnection
return $this->_conn->exec($statement); 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. * Fetch the SQLSTATE associated with the last database operation.
* *
@ -708,73 +644,6 @@ class Connection implements DriverConnection
return $this->_conn->lastInsertId($seqName); 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. * Gets the wrapped driver connection.
* *
@ -802,34 +671,6 @@ class Connection implements DriverConnection
return $this->_schemaManager; 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 * Converts a given value to its database representation according to the conversion
* rules of a specific DBAL mapping type. * rules of a specific DBAL mapping type.

View File

@ -36,9 +36,6 @@ interface Connection
function quote($input, $type=\PDO::PARAM_STR); function quote($input, $type=\PDO::PARAM_STR);
function exec($statement); function exec($statement);
function lastInsertId($name = null); function lastInsertId($name = null);
function beginTransaction();
function commit();
function rollBack();
function errorCode(); function errorCode();
function errorInfo(); function errorInfo();
} }

View File

@ -26,7 +26,7 @@ namespace Doctrine\DBAL\Driver\OCI8;
* *
* @since 2.0 * @since 2.0
*/ */
class OCI8Connection implements \Doctrine\DBAL\Driver\Connection class OCI8Connection implements \Doctrine\DBAL\Driver\Connection, \Doctrine\DBAL\Driver\Transaction
{ {
private $_dbh; private $_dbh;

View File

@ -29,7 +29,7 @@ use \PDO;
* *
* @since 2.0 * @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) public function __construct($dsn, $user = null, $password = null, array $options = null)
{ {

View File

@ -26,7 +26,7 @@ namespace Doctrine\DBAL\Driver\PDOMsSql;
* *
* @since 2.0 * @since 2.0
*/ */
class Connection extends \PDO implements \Doctrine\DBAL\Driver\Connection class Connection extends \Doctrine\DBAL\Driver\PDOConnection
{ {
/** /**
* Performs the rollback. * Performs the rollback.

View File

@ -0,0 +1,42 @@
<?php
/*
* $Id$
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* This software consists of voluntary contributions made by many individuals
* and is licensed under the LGPL. For more information, see
* <http://www.doctrine-project.org>.
*/
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 <kontakt@beberlei.de>
* @author Guilherme Blanco <guilhermeblanco@hotmail.com>
* @author Jonathan Wage <jonwage@gmail.com>
* @author Roman Borschel <roman@code-factory.org>
*/
interface Transaction
{
function beginTransaction();
function commit();
function rollBack();
}

View File

@ -23,6 +23,7 @@ namespace Doctrine\DBAL\Platforms;
use Doctrine\DBAL\DBALException, use Doctrine\DBAL\DBALException,
Doctrine\DBAL\Connection, Doctrine\DBAL\Connection,
Doctrine\DBAL\Transaction,
Doctrine\DBAL\Types, Doctrine\DBAL\Types,
Doctrine\DBAL\Schema\Table, Doctrine\DBAL\Schema\Table,
Doctrine\DBAL\Schema\Index, Doctrine\DBAL\Schema\Index,
@ -1445,13 +1446,13 @@ abstract class AbstractPlatform
protected function _getTransactionIsolationLevelSQL($level) protected function _getTransactionIsolationLevelSQL($level)
{ {
switch ($level) { switch ($level) {
case Connection::TRANSACTION_READ_UNCOMMITTED: case Transaction::READ_UNCOMMITTED:
return 'READ UNCOMMITTED'; return 'READ UNCOMMITTED';
case Connection::TRANSACTION_READ_COMMITTED: case Transaction::READ_COMMITTED:
return 'READ COMMITTED'; return 'READ COMMITTED';
case Connection::TRANSACTION_REPEATABLE_READ: case Transaction::REPEATABLE_READ:
return 'REPEATABLE READ'; return 'REPEATABLE READ';
case Connection::TRANSACTION_SERIALIZABLE: case Transaction::SERIALIZABLE:
return 'SERIALIZABLE'; return 'SERIALIZABLE';
default: default:
throw new \InvalidArgumentException('Invalid isolation level:' . $level); throw new \InvalidArgumentException('Invalid isolation level:' . $level);
@ -1584,11 +1585,11 @@ abstract class AbstractPlatform
* Gets the default transaction isolation level of the platform. * Gets the default transaction isolation level of the platform.
* *
* @return integer The default isolation level. * @return integer The default isolation level.
* @see Doctrine\DBAL\Connection\TRANSACTION_* constants. * @see Doctrine\DBAL\Transaction constants.
*/ */
public function getDefaultTransactionIsolationLevel() public function getDefaultTransactionIsolationLevel()
{ {
return Connection::TRANSACTION_READ_COMMITTED; return Transaction::READ_COMMITTED;
} }
/* supports*() metods */ /* supports*() metods */

View File

@ -146,12 +146,12 @@ class OraclePlatform extends AbstractPlatform
protected function _getTransactionIsolationLevelSQL($level) protected function _getTransactionIsolationLevelSQL($level)
{ {
switch ($level) { switch ($level) {
case \Doctrine\DBAL\Connection::TRANSACTION_READ_UNCOMMITTED: case \Doctrine\DBAL\Transaction::READ_UNCOMMITTED:
return 'READ UNCOMMITTED'; return 'READ UNCOMMITTED';
case \Doctrine\DBAL\Connection::TRANSACTION_READ_COMMITTED: case \Doctrine\DBAL\Transaction::READ_COMMITTED:
return 'READ COMMITTED'; return 'READ COMMITTED';
case \Doctrine\DBAL\Connection::TRANSACTION_REPEATABLE_READ: case \Doctrine\DBAL\Transaction::REPEATABLE_READ:
case \Doctrine\DBAL\Connection::TRANSACTION_SERIALIZABLE: case \Doctrine\DBAL\Transaction::SERIALIZABLE:
return 'SERIALIZABLE'; return 'SERIALIZABLE';
default: default:
return parent::_getTransactionIsolationLevelSQL($level); return parent::_getTransactionIsolationLevelSQL($level);

View File

@ -130,11 +130,11 @@ class SqlitePlatform extends AbstractPlatform
protected function _getTransactionIsolationLevelSQL($level) protected function _getTransactionIsolationLevelSQL($level)
{ {
switch ($level) { switch ($level) {
case \Doctrine\DBAL\Connection::TRANSACTION_READ_UNCOMMITTED: case \Doctrine\DBAL\Transaction::READ_UNCOMMITTED:
return 0; return 0;
case \Doctrine\DBAL\Connection::TRANSACTION_READ_COMMITTED: case \Doctrine\DBAL\Transaction::READ_COMMITTED:
case \Doctrine\DBAL\Connection::TRANSACTION_REPEATABLE_READ: case \Doctrine\DBAL\Transaction::REPEATABLE_READ:
case \Doctrine\DBAL\Connection::TRANSACTION_SERIALIZABLE: case \Doctrine\DBAL\Transaction::SERIALIZABLE:
return 1; return 1;
default: default:
return parent::_getTransactionIsolationLevelSQL($level); return parent::_getTransactionIsolationLevelSQL($level);

View File

@ -0,0 +1,229 @@
<?php
/*
* $Id$
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* This software consists of voluntary contributions made by many individuals
* and is licensed under the LGPL. For more information, see
* <http://www.doctrine-project.org>.
*/
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 <kontakt@beberlei.de>
* @author Guilherme Blanco <guilhermeblanco@hotmail.com>
* @author Jonathan Wage <jonwage@gmail.com>
* @author Roman Borschel <roman@code-factory.org>
*/
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;
}
}

View File

@ -107,6 +107,13 @@ class EntityManager
*/ */
private $_closed = false; private $_closed = false;
/**
* The ORM Entity Transaction.
*
* @var Doctrine\ORM\EntityTransaction
*/
protected $_transaction;
/** /**
* Creates a new EntityManager that operates on the given database connection * Creates a new EntityManager that operates on the given database connection
* and uses the given Configuration and EventManager implementations. * and uses the given Configuration and EventManager implementations.
@ -127,6 +134,7 @@ class EntityManager
$config->getProxyDir(), $config->getProxyDir(),
$config->getProxyNamespace(), $config->getProxyNamespace(),
$config->getAutoGenerateProxyClasses()); $config->getAutoGenerateProxyClasses());
$this->_transaction = new EntityTransaction($conn->getTransaction());
} }
/** /**
@ -149,6 +157,16 @@ class EntityManager
return $this->_metadataFactory; 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. * Gets an ExpressionBuilder used for object-oriented construction of query expressions.
* *
@ -175,7 +193,7 @@ class EntityManager
*/ */
public function beginTransaction() public function beginTransaction()
{ {
$this->_conn->beginTransaction(); $this->getTransaction()->begin();
} }
/** /**
@ -183,7 +201,7 @@ class EntityManager
*/ */
public function commit() public function commit()
{ {
$this->_conn->commit(); $this->getTransaction()->commit();
} }
/** /**
@ -192,7 +210,7 @@ class EntityManager
*/ */
public function rollback() public function rollback()
{ {
$this->_conn->rollback(); $this->getTransaction()->rollback();
$this->close(); $this->close();
} }

View File

@ -0,0 +1,154 @@
<?php
/*
* $Id$
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* This software consists of voluntary contributions made by many individuals
* and is licensed under the LGPL. For more information, see
* <http://www.doctrine-project.org>.
*/
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 <kontakt@beberlei.de>
* @author Guilherme Blanco <guilhermeblanco@hotmail.com>
* @author Jonathan Wage <jonwage@gmail.com>
* @author Roman Borschel <roman@code-factory.org>
*/
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();
}
}

View File

@ -281,11 +281,12 @@ class UnitOfWork implements PropertyChangedListener
// Now we need a commit order to maintain referential integrity // Now we need a commit order to maintain referential integrity
$commitOrder = $this->_getCommitOrder(); $commitOrder = $this->_getCommitOrder();
$tx = $this->_em->getTransaction();
$conn = $this->_em->getConnection(); try {
$tx->begin();
$conn->beginTransaction();
try {
if ($this->_entityInsertions) { if ($this->_entityInsertions) {
foreach ($commitOrder as $class) { foreach ($commitOrder as $class) {
$this->_executeInserts($class); $this->_executeInserts($class);
@ -321,11 +322,10 @@ class UnitOfWork implements PropertyChangedListener
} }
} }
$conn->commit(); $tx->commit();
} catch (\Exception $e) { } catch (\Exception $e) {
$conn->setRollbackOnly(); $tx->rollback();
$conn->rollback();
$this->_em->close();
throw $e; throw $e;
} }

View File

@ -56,12 +56,11 @@ class DriverManagerTest extends \Doctrine\Tests\DbalTestCase
public function testCustomWrapper() public function testCustomWrapper()
{ {
$wrapperMock = $this->getMock('\Doctrine\DBAL\Connection', array(), array(), '', false); $wrapperClass = 'Doctrine\Tests\Mocks\ConnectionMock';
$wrapperClass = get_class($wrapperMock);
$options = array( $options = array(
'pdo' => new \PDO('sqlite::memory:'), 'pdo' => new \PDO('sqlite::memory:'),
'wrapperClass' => $wrapperClass 'wrapperClass' => $wrapperClass,
); );
$conn = \Doctrine\DBAL\DriverManager::getConnection($options); $conn = \Doctrine\DBAL\DriverManager::getConnection($options);

View File

@ -25,7 +25,7 @@ class AllTests
$suite->addTestSuite('Doctrine\Tests\DBAL\Functional\Schema\MySqlSchemaManagerTest'); $suite->addTestSuite('Doctrine\Tests\DBAL\Functional\Schema\MySqlSchemaManagerTest');
$suite->addTestSuite('Doctrine\Tests\DBAL\Functional\Schema\PostgreSqlSchemaManagerTest'); $suite->addTestSuite('Doctrine\Tests\DBAL\Functional\Schema\PostgreSqlSchemaManagerTest');
$suite->addTestSuite('Doctrine\Tests\DBAL\Functional\Schema\OracleSchemaManagerTest'); $suite->addTestSuite('Doctrine\Tests\DBAL\Functional\Schema\OracleSchemaManagerTest');
$suite->addTestSuite('Doctrine\Tests\DBAL\Functional\ConnectionTest'); $suite->addTestSuite('Doctrine\Tests\DBAL\Functional\TransactionTest');
return $suite; return $suite;
} }

View File

@ -1,64 +0,0 @@
<?php
namespace Doctrine\Tests\DBAL\Functional;
use Doctrine\DBAL\ConnectionException;
require_once __DIR__ . '/../../TestInit.php';
class ConnectionTest extends \Doctrine\Tests\DbalFunctionalTestCase
{
public function testTransactionNestingBehavior()
{
try {
$this->_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());
}
}
}

View File

@ -0,0 +1,68 @@
<?php
namespace Doctrine\Tests\DBAL\Functional;
use Doctrine\DBAL\ConnectionException;
require_once __DIR__ . '/../../TestInit.php';
class TransactionTest extends \Doctrine\Tests\DbalFunctionalTestCase
{
public function testTransactionNestingBehavior()
{
$tx = $this->_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());
}
}
}

View File

@ -44,19 +44,19 @@ class MsSqlPlatformTest extends AbstractPlatformTestCase
{ {
$this->assertEquals( $this->assertEquals(
'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED', '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( $this->assertEquals(
'SET TRANSACTION ISOLATION LEVEL READ COMMITTED', '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( $this->assertEquals(
'SET TRANSACTION ISOLATION LEVEL REPEATABLE READ', '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( $this->assertEquals(
'SET TRANSACTION ISOLATION LEVEL SERIALIZABLE', 'SET TRANSACTION ISOLATION LEVEL SERIALIZABLE',
$this->_platform->getSetTransactionIsolationSQL(\Doctrine\DBAL\Connection::TRANSACTION_SERIALIZABLE) $this->_platform->getSetTransactionIsolationSQL(\Doctrine\DBAL\Transaction::SERIALIZABLE)
); );
} }

View File

@ -53,20 +53,20 @@ class MySqlPlatformTest extends AbstractPlatformTestCase
{ {
$this->assertEquals( $this->assertEquals(
'SET SESSION TRANSACTION ISOLATION LEVEL READ UNCOMMITTED', '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( $this->assertEquals(
'SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED', '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( $this->assertEquals(
'SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ', '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( $this->assertEquals(
'SET SESSION TRANSACTION ISOLATION LEVEL SERIALIZABLE', 'SET SESSION TRANSACTION ISOLATION LEVEL SERIALIZABLE',
$this->_platform->getSetTransactionIsolationSQL(\Doctrine\DBAL\Connection::TRANSACTION_SERIALIZABLE) $this->_platform->getSetTransactionIsolationSQL(\Doctrine\DBAL\Transaction::SERIALIZABLE)
); );
} }

View File

@ -55,19 +55,19 @@ class OraclePlatformTest extends AbstractPlatformTestCase
{ {
$this->assertEquals( $this->assertEquals(
'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED', '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( $this->assertEquals(
'SET TRANSACTION ISOLATION LEVEL READ COMMITTED', '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( $this->assertEquals(
'SET TRANSACTION ISOLATION LEVEL SERIALIZABLE', 'SET TRANSACTION ISOLATION LEVEL SERIALIZABLE',
$this->_platform->getSetTransactionIsolationSQL(\Doctrine\DBAL\Connection::TRANSACTION_REPEATABLE_READ) $this->_platform->getSetTransactionIsolationSQL(\Doctrine\DBAL\Transaction::REPEATABLE_READ)
); );
$this->assertEquals( $this->assertEquals(
'SET TRANSACTION ISOLATION LEVEL SERIALIZABLE', 'SET TRANSACTION ISOLATION LEVEL SERIALIZABLE',
$this->_platform->getSetTransactionIsolationSQL(\Doctrine\DBAL\Connection::TRANSACTION_SERIALIZABLE) $this->_platform->getSetTransactionIsolationSQL(\Doctrine\DBAL\Transaction::SERIALIZABLE)
); );
} }

View File

@ -4,7 +4,7 @@ namespace Doctrine\Tests\DBAL\Platforms;
use Doctrine\DBAL\Platforms\PostgreSqlPlatform; use Doctrine\DBAL\Platforms\PostgreSqlPlatform;
use Doctrine\DBAL\Types\Type; use Doctrine\DBAL\Types\Type;
use Doctrine\DBAL\Connection; use Doctrine\DBAL\Transaction;
require_once __DIR__ . '/../../TestInit.php'; require_once __DIR__ . '/../../TestInit.php';
@ -74,19 +74,19 @@ class PostgreSqlPlatformTest extends AbstractPlatformTestCase
{ {
$this->assertEquals( $this->assertEquals(
'SET SESSION CHARACTERISTICS AS TRANSACTION ISOLATION LEVEL READ UNCOMMITTED', 'SET SESSION CHARACTERISTICS AS TRANSACTION ISOLATION LEVEL READ UNCOMMITTED',
$this->_platform->getSetTransactionIsolationSQL(Connection::TRANSACTION_READ_UNCOMMITTED) $this->_platform->getSetTransactionIsolationSQL(Transaction::READ_UNCOMMITTED)
); );
$this->assertEquals( $this->assertEquals(
'SET SESSION CHARACTERISTICS AS TRANSACTION ISOLATION LEVEL READ COMMITTED', 'SET SESSION CHARACTERISTICS AS TRANSACTION ISOLATION LEVEL READ COMMITTED',
$this->_platform->getSetTransactionIsolationSQL(Connection::TRANSACTION_READ_COMMITTED) $this->_platform->getSetTransactionIsolationSQL(Transaction::READ_COMMITTED)
); );
$this->assertEquals( $this->assertEquals(
'SET SESSION CHARACTERISTICS AS TRANSACTION ISOLATION LEVEL REPEATABLE READ', 'SET SESSION CHARACTERISTICS AS TRANSACTION ISOLATION LEVEL REPEATABLE READ',
$this->_platform->getSetTransactionIsolationSQL(Connection::TRANSACTION_REPEATABLE_READ) $this->_platform->getSetTransactionIsolationSQL(Transaction::REPEATABLE_READ)
); );
$this->assertEquals( $this->assertEquals(
'SET SESSION CHARACTERISTICS AS TRANSACTION ISOLATION LEVEL SERIALIZABLE', 'SET SESSION CHARACTERISTICS AS TRANSACTION ISOLATION LEVEL SERIALIZABLE',
$this->_platform->getSetTransactionIsolationSQL(Connection::TRANSACTION_SERIALIZABLE) $this->_platform->getSetTransactionIsolationSQL(Transaction::SERIALIZABLE)
); );
} }

View File

@ -36,10 +36,10 @@ class SqlitePlatformTest extends AbstractPlatformTestCase
public function testGeneratesTransactionCommands() public function testGeneratesTransactionCommands()
{ {
$this->assertEquals('PRAGMA read_uncommitted = 0', $this->_platform->getSetTransactionIsolationSQL(\Doctrine\DBAL\Connection::TRANSACTION_READ_UNCOMMITTED)); $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\Connection::TRANSACTION_READ_COMMITTED)); $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\Connection::TRANSACTION_REPEATABLE_READ)); $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\Connection::TRANSACTION_SERIALIZABLE)); $this->assertEquals('PRAGMA read_uncommitted = 1', $this->_platform->getSetTransactionIsolationSQL(\Doctrine\DBAL\Transaction::SERIALIZABLE));
} }
public function testPrefersIdentityColumns() public function testPrefersIdentityColumns()

View File

@ -11,8 +11,11 @@ class ConnectionMock extends \Doctrine\DBAL\Connection
public function __construct(array $params, $driver, $config = null, $eventManager = null) public function __construct(array $params, $driver, $config = null, $eventManager = null)
{ {
parent::__construct($params, $driver, $config, $eventManager);
$this->_platformMock = new DatabasePlatformMock(); $this->_platformMock = new DatabasePlatformMock();
parent::__construct($params, $driver, $config, $eventManager);
// Override possible assignment of platform to database platform mock
$this->_platform = $this->_platformMock; $this->_platform = $this->_platformMock;
} }