Skip to content

Instantly share code, notes, and snippets.

@tlode
Last active August 29, 2015 14:13
Show Gist options
  • Save tlode/6f1fa559d67c8461ba42 to your computer and use it in GitHub Desktop.
Save tlode/6f1fa559d67c8461ba42 to your computer and use it in GitHub Desktop.
TransactionTrait
<?php
namespace Model;
use PommProject\Foundation\Session\Connection;
use PommProject\ModelManager\Exception\ModelException;
use PommProject\ModelManager\ModelLayer\ModelLayer;
trait BbTransactionTrait
{
private $savePointStack = [];
/**
* startTransaction
*
* Start a new transaction.
*
* @access protected
* @return ModelLayer $this
*/
protected function startTransaction()
{
if($this->isInTransaction())
{
$className = join('_', explode('\\', get_class($this)));
$savePoint = 'sp_'. $className.'_'. count($this->savePointStack);
array_push($this->savePointStack, $savePoint);
$this->setSavePoint($savePoint);
}
else
{
$this->executeAnonymousQuery('begin transaction');
}
return $this;
}
/**
* setSavePoint
*
* Set a savepoint in a transaction.
*
* @access protected
* @param string $name
* @return ModelLayer $this
*/
protected function setSavepoint($name)
{
return $this->sendParameter(
"savepoint %s",
$name
);
}
/**
* releaseSavepoint
*
* Drop a savepoint.
*
* @access protected
* @param string $name
* @return ModelLayer $this
*/
protected function releaseSavepoint($name)
{
return $this->sendParameter(
"release savepoint %s",
$name
);
}
/**
* rollbackTransaction
*
* Rollback a transaction. If a name is specified, the transaction is
* rollback to the given savepoint. Otherwise, the whole transaction is
* rollback.
*
* @access protected
* @param string|null $name
* @return ModelLayer $this
*/
protected function rollbackTransaction($name = null)
{
if(count($this->savePointStack) && $this->isInTransaction())
{
if ($name !== null) {
do {
$savePoint = array_pop($this->savePointStack);
} while ($savePoint && $savePoint !== $name);
$savePoint = $name;
} else {
$savePoint = array_pop($this->savePointStack);
}
$sql = sprintf("rollback to savepoint %s", $this->escapeIdentifier($savePoint));
}
else
{
$this->savePointStack = array();
$sql = "rollback transaction";
}
$this->executeAnonymousQuery($sql);
return $this;
}
/**
* commitTransaction
*
* Commit a transaction.
*
* @access protected
* @return ModelLayer $this
*/
protected function commitTransaction()
{
if(count($this->savePointStack) && $this->isInTransaction())
{
$savePoint = array_pop($this->savePointStack);
$this->releaseSavePoint($savePoint);
}
else
{
$this->savePointStack = array();
$this->executeAnonymousQuery('commit transaction');
}
return $this;
}
/**
* isInTransaction
*
* Tell if a transaction is open or not.
*
* @see Cient
* @access protected
* @return bool
*/
protected function isInTransaction()
{
$status = $this
->getSession()
->getConnection()
->getTransactionStatus();
return (bool) ($status === \PGSQL_TRANSACTION_INTRANS || $status === \PGSQL_TRANSACTION_INERROR || $status === \PGSQL_TRANSACTION_ACTIVE);
}
/**
* isTransactionOk
*
* In Postgresql, an error during a transaction cancels all the queries and
* rollback the transaction on commit. This method returns the current
* transaction's status. If no transactions are open, it returns null.
*
* @access public
* @return bool|null
*/
protected function isTransactionOk()
{
if (!$this->isInTransaction()) {
return null;
}
$status = $this
->getSession()
->getConnection()
->getTransactionStatus();
return (bool) ($status === \PGSQL_TRANSACTION_INTRANS);
}
/**
* setDeferrable
*
* Set given constraints to deferred/immediate in the current transaction.
* This applies to constraints being deferrable or deferred by default.
* If the keys is an empty arrays, ALL keys will be set at the given state.
*
* @see http://www.postgresql.org/docs/9.0/static/sql-set-constraints.html
*
* @access protected
* @param array $keys
* @param string $state
* @return ModelLayer $this
* @throws ModelException if not valid state
*/
protected function setDeferrable(array $keys = [], $state)
{
if (count($keys) === 0) {
$string = 'ALL';
} else {
$string = join(
', ',
array_map(
function ($key) {
$this->escapeIdentifier($key);
},
$keys
)
);
}
if (!in_array($state, [Connection::CONSTRAINTS_DEFERRED, Connection::CONSTRAINTS_IMMEDIATE])) {
throw new ModelException(sprintf("'%s' is not a valid constraints modifier.", $state));
}
$this->executeAnonymousQuery(
sprintf(
"set constraints %s %s",
$string,
$state
)
);
return $this;
}
/**
* setTransactionIsolationLevel
*
* Transaction isolation level tells postgresql how to manage with the
* current transaction. The default is "READ COMMITED".
*
* @see http://www.postgresql.org/docs/9.0/static/sql-set-transaction.html
*
* @access protected
* @param $isolaton_level
* @return ModelLayer $this
* @throws ModelException
* @internal param string $state
* @throw ModelException if not valid isolation level
*/
protected function setTransactionIsolationLevel($isolaton_level)
{
if (!in_array(
$isolaton_level,
[
Connection::ISOLATION_READ_COMMITTED,
Connection::ISOLATION_READ_REPEATABLE,
Connection::ISOLATION_SERIALIZABLE
]
)
) {
throw new ModelException(sprintf("'%s' is not a valid transaction isolation level."));
}
return $this->sendParameter(
"set transaction isolation level %s",
'',
$isolaton_level
);
}
/**
* setTransactionAccessMode
*
* Transaction access modes tell Postgresql if transaction are able to
* write or read only.
*
* @see http://www.postgresql.org/docs/9.0/static/sql-set-transaction.html
*
* @access protected
* @param string $access_mode
* @return ModelLayer $this
* @throws ModelException
* @throw ModelException if not valid access mode
*/
protected function setTransactionAccessMode($access_mode)
{
if (!in_array(
$access_mode,
[Connection::ACCESS_MODE_READ_ONLY, Connection::ACCESS_MODE_READ_WRITE]
)
) {
throw new ModelException(sprintf("'%s' is not a valid transaction access mode.", $access_mode));
}
return $this->sendParameter(
"set transaction %s",
'',
$access_mode
);
}
/**
* sendParameter
*
* Send a parameter to the server.
* The parameter MUST have been properly checked and escpaed if needed as
* it is going to be passed AS IS to the server. Sending untrusted
* parameters may lead to potential SQL injection.
*
* @access private
* @param string $sql
* @param string $identifier
* @param string $parameter
* @return ModelLayer $this
*/
private function sendParameter($sql, $identifier, $parameter = null)
{
$this
->executeAnonymousQuery(
sprintf(
$sql,
$this->escapeIdentifier($identifier),
$parameter
)
);
return $this;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment