Categories

Transactions in Drupal 7

14.02.2014
Author:

Transaction is a set of consecutive operations with the database, which comprises a logical unit of work with data. The transaction can be executed both completely and successfully, while maintaining data integrity and being independent from the transactions which may be performed simultaneously; or can not be executed at all. In the latter case it should not cause any changes.

Since the 7th version, Drupal provides an opportunity to support transactions including those databases that do not support them. However if you try to execute two transactions at the same time it can complicate the process of their performance. In this case their behavior will depend on the type of database used.

The same problem arises with embedding in C/C++. If the code has already locked the table A and tries to block it again, you’ll get stumped. But if a Drupal developer writes a code that checks whether there is any blocking and makes the second attempt only in the case of its absence, then such misunderstandings can be avoided. However this can lead to premature blocking removal while you may still need it.

There is a similar problem in SQL. E.g. if your code is running in a transaction and at the same moment a new one starts, we can get unpredictable outcome when one transaction is executed and another begins.

Java solved the embedding problem with blocking in another way. Supporting embedded structures Java allows you to mark a function as ‘synchronized’, which makes the latter wait until the blocking is possible, or remove the lock when it is no longer necessary.

Although it is not possible to write "transaction" function in PHP, one can follow the Java embedded logic using objects with constructors and destructors. For this reason Drupal designed a wrapper class for creating and managing transactions in databases - class DatabaseTransaction

It is used in the function "$txn = db_transaction();" as the first operation to make transaction from the current function, in which it is invoked.

To start a new transaction it is necessary to write $txn = db_transaction(); in the code. The transaction will remain open for as long as the variable $txn is executed. When the variable $txn is removed, the transaction will be performed. If your transaction is embedded into another one, then Drupal will perform each operation separately and an external transaction will be completed only when all the objects, that is all requests, are completed successfully.

When one of the queries in a transaction fails, a "rollback" (an operation that returns the data to its initial state before the transaction starts) of all changes happens.

Example:

<?php
function my_transaction_function() {
  // The transaction opens here.
  $transaction = db_transaction();
  try {
	$id = db_insert('example')
  	->fields(array(
    	'field1' => 'mystring',
    	'field2' => 5,
  	))
  	->execute();
	my_other_function($id);
	return $id;
  }
  catch (Exception $e) {
	$transaction->rollback();
	watchdog_exception('my_type', $e);
  }
  // $transaction goes out of scope here.  Unless it was rolled back, 
  // it gets automatically commited here.
}


function my_other_function($id) {
  // The transaction is still open here.
  if ($id % 2 == 0) {
	db_update('example')
  	->condition('id', $id)
  	->fields(array('field2' => 10))
  	->execute();
  }
}
?>

If it is necessary to finish one of the transactions prematurely, one needs to remove the variable $transaction using the function unset().

<?php
  unset($transaction);
?>

Let’s work with the transaction using an example.

Imagine a situation when it is required to process a large amount of data for users and the operation for them should be grouped. Problem: withdrawal and transfer of funds from internal user accounts on the basis of records in the temporary table.

<?php
  function update_users_accounts() {
    // The transaction opens here.
    $accounts_transaction = db_transaction();
    try {
      // Select all users, that should be processed.
      $query = db_select(‘account_tmp_table’, 'at');
      $query->fields('at', array('uid', ‘decrease’, ‘increase’));
      $query->condition('at.timestamp', $time, '>');
      $result = $query->execute();
      while($record = $result->fetchAssoc()) {
        // Remove money from  user account.
        decrease_users_accounts($record[‘uid’], $record[‘decrease’]);
        // Add money to user account.
        increase_users_accounts($record[‘uid’], $record[‘increase’]);
        // Delete temp record from table.
        delete_users_account_record($record[‘uid’]);
      }	
      return TRUE;
    }
    catch (Exception $e) {
      $accounts_transaction->rollback();
      watchdog_exception('users_accounts', $e);
    }
  // $transaction goes out of scope here.  Unless it was rolled back, 
  // it gets automatically commited here.
  }

  function decrease_users_accounts($uid, $decrease) {
    // The transaction opens here.
    // Get current user account.
    $current_account = get_user_account($uid);
    $updated_account = $current_account - $decrease;
    // Update user's account record.
    db_update('account_data_table')
      ->condition('uid', $uid)
      ->fields(array('account' => $updated_account))
      ->execute();
  }
  
  function increase_users_accounts($uid, $increase) {
    // Get current user account.
    $current_account = get_user_account($uid);
    $updated_account = $current_account + $increase;
    // Update user's account record.
    db_update('account_data_table')
      ->condition('uid', $uid)
      ->fields(array('account' => $updated_account))
      ->execute();
  }
  
  function delete_users_account_record($uid) {
    // The transaction opens here.
    db_delete('account_tmp_table')
      ->condition('uid', $uid)
      ->execute();
  }
}

?>

Such function realization through transactions will prevent the changes in the user account by another functionality during the code execution, as well as will guarantee the integrity of the operation by calling the rollback method which returns all data to its original state, if some errors occur.

This article goes about transactions and how Drupal can support them. For more detailed information, see documentation.

8 votes, Rating: 5

Read also

1

Apps is a module that can be looked at as a subsequent step in the evolution of...

2

The look of the login/registration form can now be changed using administration tool. This is possible with our Customize login form module.

3

In this article we will speak about Drupal core installation as a submodule. We will not dwell upon the very concept of a submodule here for long, nor try to consider its dis/advantages. What we...

4

At times, content importing appears to be the task which is far from trivial. Writing the import assignment out "from scratch" when aimed at covering each and every instance can't be practical,...

5

The article describes the CMS Drupal deployment process as performed with use of Oracle DB on Debian server.

Subscribe to our blog updates