Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[8.x] Transaction aware code execution #35373

Merged
merged 7 commits into from Nov 27, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
47 changes: 47 additions & 0 deletions src/Illuminate/Database/Concerns/ManagesTransactions.php
Expand Up @@ -3,6 +3,7 @@
namespace Illuminate\Database\Concerns;

use Closure;
use RuntimeException;
use Throwable;

trait ManagesTransactions
Expand Down Expand Up @@ -42,6 +43,10 @@ public function transaction(Closure $callback, $attempts = 1)
try {
if ($this->transactions == 1) {
$this->getPdo()->commit();

if ($this->transactionsManager) {
$this->transactionsManager->commit($this->getName());
}
}

$this->transactions = max(0, $this->transactions - 1);
Expand Down Expand Up @@ -78,6 +83,12 @@ protected function handleTransactionException(Throwable $e, $currentAttempt, $ma
$this->transactions > 1) {
$this->transactions--;

if ($this->transactionsManager) {
$this->transactionsManager->rollback(
$this->getName(), $this->transactions
);
}

throw $e;
}

Expand Down Expand Up @@ -107,6 +118,12 @@ public function beginTransaction()

$this->transactions++;

if ($this->transactionsManager) {
$this->transactionsManager->begin(
$this->getName(), $this->transactions
);
}

$this->fireConnectionEvent('beganTransaction');
}

Expand Down Expand Up @@ -176,6 +193,10 @@ public function commit()
{
if ($this->transactions == 1) {
$this->getPdo()->commit();

if ($this->transactionsManager) {
$this->transactionsManager->commit($this->getName());
}
}

$this->transactions = max(0, $this->transactions - 1);
Expand Down Expand Up @@ -241,6 +262,12 @@ public function rollBack($toLevel = null)

$this->transactions = $toLevel;

if ($this->transactionsManager) {
$this->transactionsManager->rollback(
$this->getName(), $this->transactions
);
}

$this->fireConnectionEvent('rollingBack');
}

Expand Down Expand Up @@ -275,6 +302,12 @@ protected function handleRollBackException(Throwable $e)
{
if ($this->causedByLostConnection($e)) {
$this->transactions = 0;

if ($this->transactionsManager) {
$this->transactionsManager->rollback(
$this->getName(), $this->transactions
);
}
}

throw $e;
Expand All @@ -289,4 +322,18 @@ public function transactionLevel()
{
return $this->transactions;
}

/**
* Execute the callback after a transaction commits.
*
* @return void
*/
public function afterCommit($callback)
{
if ($this->transactionsManager) {
return $this->transactionsManager->addCallback($callback);
}

throw new RuntimeException('Transactions Manager has not been set.');
}
}
30 changes: 30 additions & 0 deletions src/Illuminate/Database/Connection.php
Expand Up @@ -112,6 +112,13 @@ class Connection implements ConnectionInterface
*/
protected $transactions = 0;

/**
* The transaction manager instance.
*
* @var \Illuminate\Database\DatabaseTransactionsManager
*/
protected $transactionsManager;

/**
* Indicates if changes have been made to the database.
*
Expand Down Expand Up @@ -1151,6 +1158,29 @@ public function unsetEventDispatcher()
$this->events = null;
}

/**
* Set the transaction manager instance on the connection.
*
* @param \Illuminate\Database\DatabaseTransactionsManager $manager
* @return $this
*/
public function setTransactionManager($manager)
{
$this->transactionsManager = $manager;

return $this;
}

/**
* Unset the event transaction manager for this connection.
*
* @return void
*/
public function unsetTransactionManager()
{
$this->transactionsManager = null;
}

/**
* Determine if the connection is in a "dry run".
*
Expand Down
4 changes: 4 additions & 0 deletions src/Illuminate/Database/DatabaseManager.php
Expand Up @@ -174,6 +174,10 @@ protected function configure(Connection $connection, $type)
$connection->setEventDispatcher($this->app['events']);
}

if ($this->app->bound('db.transactions')) {
$connection->setTransactionManager($this->app['db.transactions']);
}

// Here we'll set a reconnector callback. This reconnector can be any callable
// so we will set a Closure to reconnect from this manager with the name of
// the connection, which will allow us to reconnect from the connections.
Expand Down
4 changes: 4 additions & 0 deletions src/Illuminate/Database/DatabaseServiceProvider.php
Expand Up @@ -71,6 +71,10 @@ protected function registerConnectionServices()
$this->app->bind('db.connection', function ($app) {
return $app['db']->connection();
});

$this->app->singleton('db.transactions', function ($app) {
return new DatabaseTransactionsManager();
});
}

/**
Expand Down
73 changes: 73 additions & 0 deletions src/Illuminate/Database/DatabaseTransactionRecord.php
@@ -0,0 +1,73 @@
<?php

namespace Illuminate\Database;

class DatabaseTransactionRecord
{
/**
* The name of the database connection.
*
* @var string
*/
public $connection;

/**
* The transaction level.
*
* @var int
*/
public $level;

/**
* The callbacks that should be executed after committing.
*
* @var array
*/
protected $callbacks = [];

/**
* Create a new database transaction record instance.
*
* @param string $connection
* @param int $level
* @retunr void
*/
public function __construct($connection, $level)
{
$this->connection = $connection;
$this->level = $level;
}

/**
* Register a callback to be executed after committing.
*
* @param callable $callback
* @return void
*/
public function addCallback($callback)
{
$this->callbacks[] = $callback;
}

/**
* Execute all of the callbacks.
*
* @return void
*/
public function executeCallbacks()
{
foreach ($this->callbacks as $callback) {
call_user_func($callback);
}
}

/**
* Get all of the callbacks.
*
* @return array
*/
public function getCallbacks()
{
return $this->callbacks;
}
}
96 changes: 96 additions & 0 deletions src/Illuminate/Database/DatabaseTransactionsManager.php
@@ -0,0 +1,96 @@
<?php

namespace Illuminate\Database;

class DatabaseTransactionsManager
{
/**
* All the recorded transactions.
*
* @var \Illuminate\Support\Collection
*/
protected $transactions;

/**
* Create a new database transactions manager instance.
*
* @return void
*/
public function __construct()
{
$this->transactions = collect();
}

/**
* Start a new database transaction.
*
* @param string $connection
* @param int $level
* @return void
*/
public function begin($connection, $level)
{
$this->transactions->push(
new DatabaseTransactionRecord($connection, $level)
);
}

/**
* Rollback the active database transaction.
*
* @param string $connection
* @param int $level
* @return void
*/
public function rollback($connection, $level)
{
$this->transactions = $this->transactions->reject(function ($transaction) use ($connection, $level) {
return $transaction->connection == $connection &&
$transaction->level > $level;
})->values();
}

/**
* Commit the active database transaction.
*
* @param string $connection
* @return void
*/
public function commit($connection)
{
$this->transactions = $this->transactions->reject(function ($transaction) use ($connection) {
if ($transaction->connection == $connection) {
$transaction->executeCallbacks();

return true;
}

return false;
})->values();
}

/**
* Register a transaction callback.
*
* @param callable $callback
* @return void.
*/
public function addCallback($callback)
{
if ($current = $this->transactions->last()) {
return $current->addCallback($callback);
}

call_user_func($callback);
}

/**
* Get all the transactions.
*
* @return \Illuminate\Support\Collection
*/
public function getTransactions()
{
return $this->transactions;
}
}