diff --git a/user_guide_src/source/database/transactions.rst b/user_guide_src/source/database/transactions.rst index 9924c494c415..b0afe0b7c75f 100644 --- a/user_guide_src/source/database/transactions.rst +++ b/user_guide_src/source/database/transactions.rst @@ -180,6 +180,31 @@ This is useful for side effects that should only happen after committed data is visible, such as dispatching a queued job or sending a notification, and for cleanup that should only happen after a real rollback. +Deferring Side Effects +---------------------- + +Code that changes external state should usually not run in the middle of a +transaction. If the transaction rolls back after the side effect has already +run, the application may send a notification for data that was never saved, +invalidate a cache for a write that did not persist, or start background work +that cannot find the committed row it expects. + +Register those side effects with ``afterCommit()`` instead: + +.. literalinclude:: transactions/013.php + +Use ``afterRollback()`` for cleanup that should happen only when the transaction +does not commit, such as removing a temporary file created before the database +write: + +.. literalinclude:: transactions/014.php + +Model callbacks such as ``afterInsert`` and ``afterUpdate`` run after the Model +query has executed, but not necessarily after the surrounding transaction has +committed. If a Model callback needs to run a side effect only after commit, it +should register that work with the database connection's ``afterCommit()`` +method while an active transaction is already open. + Callbacks run after the database transaction has already committed or rolled back. If a callback throws an exception, that exception bubbles to the caller, but the transaction outcome is not changed. diff --git a/user_guide_src/source/database/transactions/013.php b/user_guide_src/source/database/transactions/013.php new file mode 100644 index 000000000000..05618c3c704c --- /dev/null +++ b/user_guide_src/source/database/transactions/013.php @@ -0,0 +1,18 @@ +db->transaction(static function ($db) use ($order): int { + $db->table('orders')->insert($order); + $orderId = $db->insertID(); + + $db->afterCommit(static function () use ($orderId): void { + service('cache')->delete('orders_list'); + Events::trigger('order_created', $orderId); + + // Dispatch a queued job or send a notification here. + // The new order is committed and visible to other database connections. + }); + + return $orderId; +}); diff --git a/user_guide_src/source/database/transactions/014.php b/user_guide_src/source/database/transactions/014.php new file mode 100644 index 000000000000..ce39da2fb0fc --- /dev/null +++ b/user_guide_src/source/database/transactions/014.php @@ -0,0 +1,11 @@ +db->transaction(static function ($db) use ($temporaryPath, $record): void { + $db->afterRollback(static function () use ($temporaryPath): void { + if (is_file($temporaryPath)) { + unlink($temporaryPath); + } + }); + + $db->table('documents')->insert($record); +});