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 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 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
You can’t perform that action at this time.