Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
[Laravel] Nested transactions wrapper class
<?php
/**
* Class for Laravel 3.x that provides ability to use nested transactions via SAVEPOINTs
* (inspired from http://www.kennynet.co.uk/2008/12/02/php-pdo-nested-transactions/).
* For now, only the default connection is supported.
*
* Useful in testing, when you want to start a transaction before running a test
* and revert all its changes after it finished.
*
* To use this, you need to replace value under "DB" key in `application.aliases` config
* with this class name. After this, you can continue to use Laravel-style transactions
* (`DB::transaction`), but you need to replace all "raw PDO" transaction calls
* (such as `DB::connection()->pdo->beginTransaction()`) with alternatives provided
* by this class.
*/
class NestedTransactor extends \Laravel\Database {
protected static $supported_drivers = ['pgsql', 'mysql', 'mysqli', 'sqlite'];
protected static $transaction_level = 0;
protected static function get_pdo() {
return static::connection()->pdo;
}
protected static function nestable() {
$current_driver = static::get_pdo()->getAttribute(\PDO::ATTR_DRIVER_NAME);
return in_array($current_driver, static::$supported_drivers);
}
protected static function process_pdo($pdo_method, $sql) {
$pdo = static::get_pdo();
if (static::$transaction_level == 0 || !static::nestable()) {
$pdo->{$pdo_method}();
} else {
$pdo->exec($sql);
}
}
public static function begin_transaction() {
static::process_pdo('beginTransaction', 'SAVEPOINT LEVEL'.static::$transaction_level);
static::$transaction_level++;
}
public static function commit_transaction() {
static::$transaction_level--;
static::process_pdo('commit', 'RELEASE SAVEPOINT LEVEL'.static::$transaction_level);
}
public static function rollback_transaction() {
static::$transaction_level--;
static::process_pdo('rollBack', 'ROLLBACK TO SAVEPOINT LEVEL'.static::$transaction_level);
}
/**
* Redefined transaction method of Laravel\Database\Connection
* @param callback $callback
* @return void
*/
public static function transaction($callback) {
static::begin_transaction();
try {
call_user_func($callback);
} catch (\Exception $e) {
static::rollback_transaction();
throw $e;
}
return static::commit_transaction();
}
}
@jan-j

This comment has been minimized.

Copy link

@jan-j jan-j commented Aug 29, 2013

Really useful. Thanks.

To everybody that will use this class not under global namespace:
Check if you prepend \ before class used in all these methods(or other way ensure that you're using right classes). Trying to retrieve PDO::ATTR_DRIVER_NAME without global namespace prefix will trigger error and I caught that quickly. But I missed Exception in catch in transaction method and it was really unpleasent bug, which caused some unrelated with database functions of laravel to not work as it supposed to. I spent half an hour to finally found the real cause. So watch out :)

@neoascetic

This comment has been minimized.

Copy link
Owner Author

@neoascetic neoascetic commented Sep 5, 2013

Thanks for the note, @jan-j. Just have added global namespace prefix.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment