Transactions
Moodle allows data manipulation to take place within a database transaction, known as a Delegated transaction. This allows you to perform CRUD1 operations, and roll them back if a failure takes place.
General principles
- These delegated transactions work in a way that, when nested, the outer levels have control over the inner ones.
- Code should not rely on a rollback happening. It is only a measure to reduce (not to eliminate) DB2 garbled information
- Any code using transactions that result in unfinished, unbalanced, or finished twice transactions will generate a
transaction_exception
and the DB will perform a rollback - If one transaction (at any level) has been marked for
rollback()
there will not be any method to change it. Finally Moodle will perform the DB rollback - If one transaction (at any level) has been marked for
allow_commit()
it will be possible to change that status torollback()
in any outer level - It will be optional to catch exceptions when using transactions, but if they are caught, then it is mandatory to mark the transaction for
rollback()
- Any explicit
rollback()
call will pass the exception originating from it, as inrollback($exception)
, to be re-thrown
The API
-
All the handling must go, exclusively, to a
moodle_database
object, leaving real drivers only implementing (protected) the old begin/commit/rollback_sql() functions -
One array of objects of type
moodle_transaction
will be stored / checked from$DB
-
$DB
will be the responsible to instantiate / accumulate / pair / comparemoodle_transaction
s -
Each
moodle_transaction
will be able to set the global mark for rollback. Commit won't change anything -
Inner-most commit/rollback will printout one complete stack of
moodle_transaction
s information if we are underDEBUG_DEVELOPER
and the new settingdelegatedtransactionsdebug
is enabled -
Normal usage of the moodle_transaction will be:
$transaction = $DB->start_delegated_transaction();
// Perform some $DB stuff
$transaction->allow_commit(); -
If, for any reason, the developer needs to catch exceptions when using transactions, it will be mandatory to use it in this way:
try {
$transaction = $DB->start_delegated_transaction();
// Perform some $DB stuff.
$transaction->allow_commit();
} catch (Exception $e) {
// Extra cleanup steps.
// Re-throw exception after commiting.
$transaction->rollback($e);
} -
In order to be able to keep some parts of code out from top transactions completely, if we know it can lead to problems, we can use:
// Check to confirm we aren't using transactions at this point.
// This will throw an exception if a transaction is found.
$DB->transactions_forbidden();
The Flow
- Any default exception handler will:
- Catch uncaught transaction_exception exceptions
- Properly perform the DB rollback
- debug/error/log honouring related settings
- inform with as many details as possible (token, place... whatever)
- Any "footer" (meaning some place before ending
<html>
output) will:- Detect "in-transaction" status
- Let execution continue, transaction is automatically rolled back in
$DB->dispose()
- inform with as many details as possible (token, place... whatever)
$DB->dispose()
will:- Detect "in-transaction" status
- log error (not possible to honour settings!)
- Properly perform the full DB rollback