Skip to content

Commit 2cc937a

Browse files
authored
feat: add closure-based database transaction helper (#10148)
1 parent 2aa8491 commit 2cc937a

7 files changed

Lines changed: 454 additions & 1 deletion

File tree

system/Database/BaseConnection.php

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1066,6 +1066,52 @@ public function afterRollback(callable $callback): static
10661066
return $this;
10671067
}
10681068

1069+
/**
1070+
* Run the callback inside a transaction.
1071+
*
1072+
* @template TReturn
1073+
*
1074+
* @param callable(self): TReturn $callback
1075+
*
1076+
* @return false|TReturn
1077+
*/
1078+
public function transaction(callable $callback): mixed
1079+
{
1080+
if (! $this->transEnabled) {
1081+
return $callback($this);
1082+
}
1083+
1084+
if (! $this->transBegin()) {
1085+
return false;
1086+
}
1087+
1088+
try {
1089+
$result = $callback($this);
1090+
} catch (Throwable $e) {
1091+
try {
1092+
$this->transRollback();
1093+
} catch (Throwable $rollbackException) {
1094+
log_message('error', 'Database: Transaction callback threw an exception before rollback failed: ' . $e);
1095+
1096+
throw $rollbackException;
1097+
} finally {
1098+
if ($this->transDepth > 0) {
1099+
$this->transStatus = false;
1100+
} elseif ($this->transStrict === false) {
1101+
$this->transStatus = true;
1102+
}
1103+
}
1104+
1105+
throw $e;
1106+
}
1107+
1108+
if (! $this->transComplete()) {
1109+
return false;
1110+
}
1111+
1112+
return $result;
1113+
}
1114+
10691115
/**
10701116
* Begin Transaction
10711117
*/

system/Database/ConnectionInterface.php

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,17 @@ public function afterCommit(callable $callback): static;
133133
*/
134134
public function afterRollback(callable $callback): static;
135135

136+
/**
137+
* Run the callback inside a transaction.
138+
*
139+
* @template TReturn
140+
*
141+
* @param callable(self): TReturn $callback
142+
*
143+
* @return false|TReturn
144+
*/
145+
public function transaction(callable $callback): mixed;
146+
136147
/**
137148
* Returns an instance of the query builder for this connection.
138149
*

tests/system/Database/BaseConnectionTest.php

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -624,6 +624,40 @@ protected function _transRollback(): bool
624624
$this->assertSame(['rolled back'], $callbacks);
625625
}
626626

627+
public function testTransactionReturnsFalseWhenTransactionCannotBegin(): void
628+
{
629+
$callbackRan = false;
630+
631+
$db = new class ($this->options) extends MockConnection {
632+
protected function _transBegin(): bool
633+
{
634+
return false;
635+
}
636+
};
637+
638+
$result = $db->transaction(static function () use (&$callbackRan): void {
639+
$callbackRan = true;
640+
});
641+
642+
$this->assertFalse($result);
643+
$this->assertFalse($callbackRan);
644+
}
645+
646+
public function testTransactionRunsCallbackWhenTransactionsAreDisabled(): void
647+
{
648+
$db = new MockConnection($this->options);
649+
$db->transOff();
650+
651+
$result = $db->transaction(static function (BaseConnection $connection): string {
652+
$connection->afterCommit(static function (): void {});
653+
654+
return 'not wrapped';
655+
});
656+
657+
$this->assertSame('not wrapped', $result);
658+
$this->assertSame(0, $db->transDepth);
659+
}
660+
627661
public function testCallFunctionDoesNotDoublePrefixAlreadyPrefixedName(): void
628662
{
629663
$db = new class ($this->options) extends MockConnection {

0 commit comments

Comments
 (0)