2006-10-13 21:29:22 +04:00
< ? 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 . phpdoctrine . com >.
*/
/**
* Doctrine_Connection_Transaction
*
* @ package Doctrine ORM
* @ url www . phpdoctrine . com
* @ license LGPL
*/
class Doctrine_Connection_Transaction implements Countable , IteratorAggregate {
/**
* Doctrine_Connection_Transaction is in open state when it is opened and there are no active transactions
*/
const STATE_OPEN = 0 ;
/**
* Doctrine_Connection_Transaction is in closed state when it is closed
*/
const STATE_CLOSED = 1 ;
/**
* Doctrine_Connection_Transaction is in active state when it has one active transaction
*/
const STATE_ACTIVE = 2 ;
/**
* Doctrine_Connection_Transaction is in busy state when it has multiple active transactions
*/
const STATE_BUSY = 3 ;
/**
* @ var Doctrine_Connection $conn the connection object
*/
private $conn ;
/**
* @ see Doctrine_Connection_Transaction :: STATE_ * constants
* @ var boolean $state the current state of the connection
*/
private $state = 0 ;
/**
* @ var integer $transaction_level the nesting level of transactions , used by transaction methods
*/
private $transaction_level = 0 ;
/**
* @ var array $invalid an array containing all invalid records within this transaction
*/
protected $invalid = array ();
/**
* @ var array $update two dimensional pending update list , the records in
* this list will be updated when transaction is committed
*/
protected $update = array ();
/**
* @ var array $insert two dimensional pending insert list , the records in
* this list will be inserted when transaction is committed
*/
protected $insert = array ();
/**
* @ var array $delete two dimensional pending delete list , the records in
* this list will be deleted when transaction is committed
*/
protected $delete = array ();
/**
* the constructor
*
* @ param Doctrine_Connection $conn
*/
public function __construct ( Doctrine_Connection $conn ) {
$this -> conn = $conn ;
$this -> state = Doctrine_Connection_Transaction :: STATE_OPEN ;
}
/**
* returns the state of this connection
*
* @ see Doctrine_Connection_Transaction :: STATE_ * constants
* @ return integer the connection state
*/
public function getState () {
return $this -> state ;
}
/**
* get the current transaction nesting level
*
* @ return integer
*/
public function getTransactionLevel () {
return $this -> transaction_level ;
}
/**
* beginTransaction
* starts a new transaction
* if the lockmode is long this starts db level transaction
*
* @ return void
*/
public function beginTransaction () {
if ( $this -> transaction_level == 0 ) {
if ( $this -> conn -> getAttribute ( Doctrine :: ATTR_LOCKMODE ) == Doctrine :: LOCK_PESSIMISTIC ) {
$this -> conn -> getAttribute ( Doctrine :: ATTR_LISTENER ) -> onPreTransactionBegin ( $this -> conn );
$this -> conn -> getDBH () -> beginTransaction ();
$this -> conn -> getAttribute ( Doctrine :: ATTR_LISTENER ) -> onTransactionBegin ( $this -> conn );
}
$this -> state = Doctrine_Connection_Transaction :: STATE_ACTIVE ;
} else {
$this -> state = Doctrine_Connection_Transaction :: STATE_BUSY ;
}
$this -> transaction_level ++ ;
}
/**
* commits the current transaction
* if lockmode is short this method starts a transaction
* and commits it instantly
*
* @ throws Doctrine_Connection_Transaction_Exception if the transaction fails at PDO level
* @ throws Doctrine_Validator_Exception if the transaction fails due to record validations
* @ return void
*/
public function commit () {
$this -> transaction_level -- ;
if ( $this -> transaction_level == 0 ) {
$this -> conn -> getAttribute ( Doctrine :: ATTR_LISTENER ) -> onPreTransactionCommit ( $this -> conn );
if ( $this -> conn -> getAttribute ( Doctrine :: ATTR_LOCKMODE ) == Doctrine :: LOCK_OPTIMISTIC ) {
$this -> conn -> getAttribute ( Doctrine :: ATTR_LISTENER ) -> onPreTransactionBegin ( $this -> conn );
$this -> conn -> getDBH () -> beginTransaction ();
2006-10-30 00:57:47 +03:00
$this -> conn -> getAttribute ( Doctrine :: ATTR_LISTENER ) -> onTransactionBegin ( $this -> conn );
2006-10-13 21:29:22 +04:00
}
try {
$this -> bulkDelete ();
} catch ( Exception $e ) {
$this -> rollback ();
2006-10-23 00:50:27 +04:00
throw new Doctrine_Connection_Transaction_Exception ( $e -> __toString ());
2006-10-13 21:29:22 +04:00
}
if ( count ( $this -> invalid ) > 0 ) {
$this -> rollback ();
$tmp = $this -> invalid ;
$this -> invalid = array ();
throw new Doctrine_Validator_Exception ( $tmp );
}
$this -> conn -> getDBH () -> commit ();
$this -> conn -> getAttribute ( Doctrine :: ATTR_LISTENER ) -> onTransactionCommit ( $this -> conn );
$this -> state = Doctrine_Connection_Transaction :: STATE_OPEN ;
$this -> invalid = array ();
} elseif ( $this -> transaction_level == 1 )
$this -> state = Doctrine_Connection_Transaction :: STATE_ACTIVE ;
}
/**
* rollback
* rolls back all transactions
*
* this method also listens to onPreTransactionRollback and onTransactionRollback
* eventlisteners
*
* @ return void
*/
public function rollback () {
$this -> conn -> getAttribute ( Doctrine :: ATTR_LISTENER ) -> onPreTransactionRollback ( $this -> conn );
$this -> delete = array ();
$this -> insert = array ();
$this -> update = array ();
$this -> transaction_level = 0 ;
$this -> conn -> getDBH () -> rollback ();
$this -> state = Doctrine_Connection_Transaction :: STATE_OPEN ;
$this -> conn -> getAttribute ( Doctrine :: ATTR_LISTENER ) -> onTransactionRollback ( $this -> conn );
}
/**
* bulkDelete
* deletes all records from the pending delete list
*
* @ return void
*/
public function bulkDelete () {
foreach ( $this -> delete as $name => $deletes ) {
$record = false ;
$ids = array ();
foreach ( $deletes as $k => $record ) {
$ids [] = $record -> getIncremented ();
$record -> assignIdentifier ( false );
}
if ( $record instanceof Doctrine_Record ) {
$params = substr ( str_repeat ( " ?, " , count ( $ids )), 0 , - 2 );
2006-10-30 00:57:47 +03:00
$query = 'DELETE FROM '
. $record -> getTable () -> getTableName ()
. ' WHERE '
. $record -> getTable () -> getIdentifier ()
. ' IN(' . $params . ')' ;
$this -> conn -> execute ( $query , $ids );
2006-10-13 21:29:22 +04:00
}
}
$this -> delete = array ();
}
/**
* updates the given record
*
* @ param Doctrine_Record $record
* @ return boolean
*/
public function update ( Doctrine_Record $record ) {
$record -> getTable () -> getAttribute ( Doctrine :: ATTR_LISTENER ) -> onPreUpdate ( $record );
$array = $record -> getPrepared ();
if ( empty ( $array ))
return false ;
$set = array ();
foreach ( $array as $name => $value ) :
$set [] = $name . " = ? " ;
if ( $value instanceof Doctrine_Record ) {
switch ( $value -> getState ()) :
case Doctrine_Record :: STATE_TCLEAN :
case Doctrine_Record :: STATE_TDIRTY :
$record -> save ();
default :
$array [ $name ] = $value -> getIncremented ();
$record -> set ( $name , $value -> getIncremented ());
endswitch ;
}
endforeach ;
$params = array_values ( $array );
$id = $record -> obtainIdentifier ();
if ( ! is_array ( $id ))
$id = array ( $id );
$id = array_values ( $id );
$params = array_merge ( $params , $id );
$sql = " UPDATE " . $record -> getTable () -> getTableName () . " SET " . implode ( " , " , $set ) . " WHERE " . implode ( " = ? AND " , $record -> getTable () -> getPrimaryKeys ()) . " = ? " ;
$stmt = $this -> conn -> getDBH () -> prepare ( $sql );
$stmt -> execute ( $params );
$record -> assignIdentifier ( true );
$record -> getTable () -> getAttribute ( Doctrine :: ATTR_LISTENER ) -> onUpdate ( $record );
return true ;
}
/**
* inserts a record into database
*
2006-10-31 13:42:46 +03:00
* @ param Doctrine_Record $record record to be inserted
2006-10-13 21:29:22 +04:00
* @ return boolean
*/
public function insert ( Doctrine_Record $record ) {
// listen the onPreInsert event
$record -> getTable () -> getAttribute ( Doctrine :: ATTR_LISTENER ) -> onPreInsert ( $record );
$array = $record -> getPrepared ();
if ( empty ( $array ))
return false ;
$table = $record -> getTable ();
$keys = $table -> getPrimaryKeys ();
$seq = $record -> getTable () -> getSequenceName ();
if ( ! empty ( $seq )) {
2006-10-31 13:42:46 +03:00
$id = $this -> nextId ( $seq );
2006-10-13 21:29:22 +04:00
$name = $record -> getTable () -> getIdentifier ();
$array [ $name ] = $id ;
}
2006-10-31 13:42:46 +03:00
$this -> conn -> insert ( $table -> getTableName (), $array );
2006-10-13 21:29:22 +04:00
if ( count ( $keys ) == 1 && $keys [ 0 ] == $table -> getIdentifier ()) {
$id = $this -> conn -> getDBH () -> lastInsertID ();
if ( ! $id )
$id = $table -> getMaxIdentifier ();
$record -> assignIdentifier ( $id );
} else
$record -> assignIdentifier ( true );
// listen the onInsert event
$table -> getAttribute ( Doctrine :: ATTR_LISTENER ) -> onInsert ( $record );
return true ;
}
/**
* adds record into pending insert list
* @ param Doctrine_Record $record
*/
public function addInsert ( Doctrine_Record $record ) {
$name = $record -> getTable () -> getComponentName ();
$this -> insert [ $name ][] = $record ;
}
/**
* adds record into penging update list
* @ param Doctrine_Record $record
*/
public function addUpdate ( Doctrine_Record $record ) {
$name = $record -> getTable () -> getComponentName ();
$this -> update [ $name ][] = $record ;
}
/**
* adds record into pending delete list
* @ param Doctrine_Record $record
*/
public function addDelete ( Doctrine_Record $record ) {
$name = $record -> getTable () -> getComponentName ();
$this -> delete [ $name ][] = $record ;
}
/**
* addInvalid
* adds record into invalid records list
*
* @ param Doctrine_Record $record
* @ return boolean false if record already existed in invalid records list ,
* otherwise true
*/
public function addInvalid ( Doctrine_Record $record ) {
if ( in_array ( $record , $this -> invalid ))
return false ;
$this -> invalid [] = $record ;
return true ;
}
/**
* returns the pending insert list
*
* @ return array
*/
public function getInserts () {
return $this -> insert ;
}
/**
* returns the pending update list
*
* @ return array
*/
public function getUpdates () {
return $this -> update ;
}
/**
* returns the pending delete list
*
* @ return array
*/
public function getDeletes () {
return $this -> delete ;
}
public function getIterator () { }
/**
* an alias for getTransactionLevel
*
* @ return integer returns the nesting level of this transaction
*/
public function count () {
return $this -> transaction_level ;
}
}