Транзакції в Drupal 7

14.02.2014
Transactions in Drupal 7
Автор:

Транзакція (англ. transaction) - це група послідовних операцій з базою даних, яка представляє собою логічну одиницю роботи з даними. Транзакція може бути як виконана повністю та успішно, зберігаючи цілісність даних та незалежно від транзакцій, які виконуються паралельно; так і не виконана взагалі, в такому випадку вона не повинна спричинити ніяких змін.

Починаючи з 7-мої версії, в Drupal з’явилася можливість підтримки транзакцій, включаючи ті бази даних, які їх не підтримують. Проте, якщо спробувати виконати дві транзакції одночасно, то їх виконання може стати складним. В цьому випадку їх поведінка залежатиме від типу бази даних.

Така ж проблема з вкладеністю виникає і в C/C++. Якщо код вже заблокував таблицю A і спробує заблокувати її повторно, то вийде глухий кут. Якщо ж ви напишете код, який буде перевіряти, чи немає блокування, і лише за його відсутності робити повторну спробу, то таких непорозумінь можна уникнути. Проте, це може призвести до передчасного зняття блокування.

В SQL існує аналогічна проблема. Якщо, наприклад, ваш код виконується в транзакції і в цей момент розпочинається нова, то ми можемо отримати непередбачуваний результат виконання поточної операції та початку нової.

Java підійшла до проблеми вкладеності з блокуванням іншим чином. Реалізуючи підтримку вкладеності структури, Java дозволяє позначити функції як "синхронізовані", що змушує останню або чекати до тих пір, поки блокування стане можливим, або зняти блокування, коли воно більше не потрібне. 

Хоча просто прописати функцію “транзакція” в PHP неможливо, проте можна наслідувати логіку вкладеності Java за допомогою об’єктів з конструкторів та деструкторів. З цією метою в Drupal було розроблено клас-оболонку для створення та управління транзакціями в базах даних - class DatabaseTransaction

Він використовується в функції "$txn = db_transaction();" в якості першої операції, щоб зробити поточну функцію, де вона викликається, транзакцією.

 

Для того, щоб розпочати нову транзакцію, необхідно просто викликати $txn = db_transaction(); у власному коді. Транзакція буде залишатися відкритою до тих пір, доки змінна $txn залишатиметься в області виконання. Коли змінна $txn буде знищена, транзакція стане виконана. Якщо ваша транзакція є вкладеністю іншої, тоді Drupal виконуватиме кожну операцію та завершить зовнішню транзакцію лише тоді, коли всі об’єкти вийдуть з області виконання, тобто всі запити пройдуть успішно.

У випадку ж, якщо один із запитів у транзакції не вдається виконати, відбувається “відкат” усіх змін, так званий “rollback” - повернення даних до вихідного стану перед початком транзакції.

Приклад:

<?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();
  }
}
?>

Якщо ж з'явилася необхідність одну з транзакцій завершити передчасно, для цього потрібно “знищити” змінну $transaction за допомогою функції unset().

<?php
  unset($transaction);
?>

Попрацюємо з транзакціями на прикладі.

Уявімо ситуацію, коли є необхідність опрацювати велику кількість даних для користувачів, операція для яких повинна бути згрупована. Задача: зняття та зарахування коштів із внутрішніх рахунків користувачів на основі записів у тимчасовій таблиці.

<?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();
  }
}

?>

Варіант реалізації через транзакції унеможливить зміни в рахунку користувача іншим функціоналом під час виконання цього коду, а також, у випадку виникнення помилок під час роботи, забезпечить збереження цілісності виконання операції за допомогою виклику методу rollback, який поверне усі дані до початкового стану.

В даній статті розглянуто можливість підтримки транзакцій при розробці сайтів на Drupal. Для більш детального ознайомлення можна скористатись документацією

Голосів: 1 Рейтинг: 5

Також по темі

1

У своїй практиці ми досить часто використовуємо Git Flow модель роботи з репозиторієм. Схема роботи з якою більш детально описана нижче.

2

Продовжуючи розгляд можливостей модуля Panels, в цьому блозі мова піде про створення власного контексту за допомогою Chaos tool suite.

3

При розробці збірки CommerceBox у нас виникло питання, як зберігати у фічі певний функціонал для apps. Метод Feature Injection вирішує цю проблему.  

4

Apps - це модуль, що позиціонується як наступний крок у розвитку...

5

В даній статті мова піде про можливість встановлення ядра Drupal 8 як сабмодуля. Ми не розважатимемо за саме поняття сабмодулів, ні за їх недоліки чи переваги, а лише зобразимо в деталях, спосіб у...

Subscribe to our blog updates