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

[10.x] Only stage committed transactions #49093

Merged
merged 19 commits into from
Nov 23, 2023
Merged
Prev Previous commit
Next Next commit
add another failing test
  • Loading branch information
taylorotwell committed Nov 22, 2023
commit 105bf6e51b8edb4fe36b6e9377ae97758a38e59a
66 changes: 33 additions & 33 deletions src/Illuminate/Database/DatabaseTransactionsManager.php
Original file line number Diff line number Diff line change
Expand Up @@ -46,23 +46,25 @@ public function begin($connection, $level)
}

/**
* Rollback the active database transaction.
* Move relevant pending transactions to a committed state.
*
* @param string $connection
* @param int $newTransactionLevel
* @param int $levelBeingCommitted
* @return void
*/
public function rollback($connection, $newTransactionLevel)
public function stageTransactions($connection, $levelBeingCommitted)
{
if ($newTransactionLevel === 0) {
$this->pendingTransactions = new Collection;
$this->committedTransactions = new Collection;
} else {
$this->pendingTransactions = $this->pendingTransactions->reject(
fn ($transaction) => $transaction->connection == $connection &&
$transaction->level > $newTransactionLevel
)->values();
}
$this->committedTransactions = $this->committedTransactions->merge(
$this->pendingTransactions->filter(
fn ($transaction) => $transaction->connection === $connection &&
$transaction->level >= $levelBeingCommitted
)
);

$this->pendingTransactions = $this->pendingTransactions->reject(
fn ($transaction) => $transaction->connection === $connection &&
$transaction->level >= $levelBeingCommitted
);
}

/**
Expand All @@ -83,40 +85,38 @@ public function commit($connection)
}

/**
* Register a transaction callback.
* Rollback the active database transaction.
*
* @param callable $callback
* @param string $connection
* @param int $newTransactionLevel
* @return void
*/
public function addCallback($callback)
public function rollback($connection, $newTransactionLevel)
{
if ($current = $this->callbackApplicableTransactions()->last()) {
return $current->addCallback($callback);
if ($newTransactionLevel === 0) {
$this->pendingTransactions = new Collection;
$this->committedTransactions = new Collection;
} else {
$this->pendingTransactions = $this->pendingTransactions->reject(
fn ($transaction) => $transaction->connection == $connection &&
$transaction->level > $newTransactionLevel
)->values();
}

$callback();
}

/**
* Move relevant pending transactions to a committed state.
* Register a transaction callback.
*
* @param string $connection
* @param int $levelBeingCommitted
* @param callable $callback
* @return void
*/
public function stageTransactions($connection, $levelBeingCommitted)
public function addCallback($callback)
{
$this->committedTransactions = $this->committedTransactions->merge(
$this->pendingTransactions->filter(
fn ($transaction) => $transaction->connection === $connection &&
$transaction->level >= $levelBeingCommitted
)
);
if ($current = $this->callbackApplicableTransactions()->last()) {
return $current->addCallback($callback);
}

$this->pendingTransactions = $this->pendingTransactions->reject(
fn ($transaction) => $transaction->connection === $connection &&
$transaction->level >= $levelBeingCommitted
);
$callback();
}

/**
Expand Down
45 changes: 45 additions & 0 deletions tests/Integration/Events/ShouldDispatchAfterCommitEventTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,51 @@ public function testChildEventsAreNotDispatchedIfParentTransactionFails()
// Should not have ran because parent transaction failed...
$this->assertFalse(ShouldDispatchAfterCommitTestEvent::$ran);
}

public function testItHandlesNestedTransactionsWhereTheSecondOneFails()
{
Event::listen(ShouldDispatchAfterCommitTestEvent::class, ShouldDispatchAfterCommitListener::class);
Event::listen(AnotherShouldDispatchAfterCommitTestEvent::class, AnotherShouldDispatchAfterCommitListener::class);

DB::transaction(function () {
DB::transaction(function () {
Event::dispatch(new ShouldDispatchAfterCommitTestEvent());
});

try {
DB::transaction(function () {
// This event should not be dispatched since the transaction is going to fail.
Event::dispatch(new AnotherShouldDispatchAfterCommitTestEvent);
throw new \Exception;
});
} catch (\Exception $e) {
}
});

$this->assertTrue(ShouldDispatchAfterCommitTestEvent::$ran);
$this->assertFalse(AnotherShouldDispatchAfterCommitTestEvent::$ran);
}

public function testItHandlesDeeplyNestedTransactions()
{
Event::listen(ShouldDispatchAfterCommitTestEvent::class, ShouldDispatchAfterCommitListener::class);

DB::transaction(function () {
try {
DB::transaction(function () {
DB::transaction(function () {
Event::dispatch(new ShouldDispatchAfterCommitTestEvent());
});

throw new \Exception;
});
} catch (\Exception $e) {
//
}
});

$this->assertFalse(ShouldDispatchAfterCommitTestEvent::$ran);
}
}

class TransactionUnawareTestEvent
Expand Down