Skip to content

Commit f3181b2

Browse files
committed
feat(database): refine transaction callback API
Signed-off-by: memleakd <[email protected]>
1 parent 2ae0104 commit f3181b2

5 files changed

Lines changed: 72 additions & 1 deletion

File tree

system/Database/ConnectionInterface.php

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,24 @@ public function query(string $sql, $binds = null);
115115
*/
116116
public function simpleQuery(string $sql);
117117

118+
/**
119+
* Register a callback to run after the outermost transaction commits.
120+
*
121+
* @param callable(): void $callback
122+
*
123+
* @return $this
124+
*/
125+
public function afterCommit(callable $callback): static;
126+
127+
/**
128+
* Register a callback to run after the outermost transaction rolls back.
129+
*
130+
* @param callable(): void $callback
131+
*
132+
* @return $this
133+
*/
134+
public function afterRollback(callable $callback): static;
135+
118136
/**
119137
* Returns an instance of the query builder for this connection.
120138
*

tests/system/Database/Live/TransactionCallbacksTest.php

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,22 @@ public function testAfterCommitRunsAfterSuccessfulTransactionCommit(): void
7070
$this->assertSame(['committed'], $callbacks);
7171
}
7272

73+
public function testAfterCommitRunsAfterManualTransactionCommit(): void
74+
{
75+
$callbacks = [];
76+
77+
$this->db->transBegin();
78+
$this->db->afterCommit(static function () use (&$callbacks): void {
79+
$callbacks[] = 'committed';
80+
});
81+
82+
$this->assertSame([], $callbacks);
83+
84+
$this->db->transCommit();
85+
86+
$this->assertSame(['committed'], $callbacks);
87+
}
88+
7389
public function testAfterCommitDoesNotRunAfterTransactionRollsBack(): void
7490
{
7591
$callbacks = [];
@@ -157,6 +173,22 @@ public function testAfterRollbackRunsAfterTransactionRollsBack(): void
157173
$this->assertSame(['rolled back'], $callbacks);
158174
}
159175

176+
public function testAfterRollbackRunsAfterManualTransactionRollback(): void
177+
{
178+
$callbacks = [];
179+
180+
$this->db->transBegin();
181+
$this->db->afterRollback(static function () use (&$callbacks): void {
182+
$callbacks[] = 'rolled back';
183+
});
184+
185+
$this->assertSame([], $callbacks);
186+
187+
$this->db->transRollback();
188+
189+
$this->assertSame(['rolled back'], $callbacks);
190+
}
191+
160192
public function testAfterRollbackDoesNotRunAfterSuccessfulTransactionCommit(): void
161193
{
162194
$callbacks = [];

user_guide_src/source/changelogs/v4.8.0.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -195,7 +195,7 @@ Testing
195195
Database
196196
========
197197

198-
- Added ``afterCommit()`` and ``afterRollback()`` transaction callbacks to database connections. These callbacks run after the outermost transaction commits or rolls back.
198+
- Added ``afterCommit()`` and ``afterRollback()`` transaction callbacks to database connections. These callbacks run after the outermost transaction commits or rolls back. See :ref:`transactions-transaction-callbacks`.
199199
- Added ``trustServerCertificate`` option to ``SQLSRV`` database connections in ``Config\Database``. Set it to ``true`` to trust the server certificate without CA validation when using encrypted connections.
200200

201201
Query Builder

user_guide_src/source/database/transactions.rst

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,8 @@ If you want an exception to be thrown when a query error occurs, you can use
111111
If a query error occurs, all the queries will be rolled backed, and a
112112
``DatabaseException`` will be thrown.
113113

114+
.. _transactions-transaction-callbacks:
115+
114116
Running Code after Commit or Rollback
115117
=====================================
116118

@@ -131,6 +133,16 @@ If the transaction commits, ``afterCommit()`` callbacks run and
131133
If no transaction is active, ``afterCommit()`` callbacks run immediately, while
132134
``afterRollback()`` callbacks are not run.
133135

136+
For example:
137+
138+
.. literalinclude:: transactions/011.php
139+
140+
.. note:: When ``afterCommit()`` is called outside an active transaction, it runs
141+
immediately. This includes calls from Model ``beforeInsert`` or
142+
``beforeUpdate`` events when the calling code has not already started a
143+
transaction, so the callback may run before the Model's insert or update
144+
query is executed.
145+
134146
This is useful for side effects that should only happen after committed data is
135147
visible, such as dispatching a queued job or sending a notification, and for
136148
cleanup that should only happen after a real rollback.
@@ -139,6 +151,10 @@ Callbacks run after the database transaction has already committed or rolled
139151
back. If a callback throws an exception, that exception bubbles to the caller,
140152
but the transaction outcome is not changed.
141153

154+
.. warning:: When multiple callbacks are registered for the same transaction
155+
outcome, they run in registration order. If one callback throws an exception,
156+
the subsequent callbacks are not run.
157+
142158
Rollback callbacks also run when CodeIgniter automatically rolls back an active
143159
transaction while handling a transaction failure or cleaning up an unfinished
144160
transaction.
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
<?php
2+
3+
$this->db->afterCommit(static function (): void {
4+
// Runs immediately because there is no active transaction.
5+
});

0 commit comments

Comments
 (0)