トランザクション
Drupalは、トランザクションをサポートしないデータベース向けの透過的なフォールバックを含め、トランザクションもサポートしています。ただし、2つのトランザクションを同時に実行しようとすると、トランザクションはかなり複雑になる可能性があります。この場合の動作もデータベースに依存します。
同様の問題は、C/C++のネストされたロックにも存在します。コードが既にロックAを取得していて、再びロックAを取得しようとすると、コードはブロックされます。既にロックを持っているかどうかをチェックし、再取得を試みないコードを書けば、デッドロックを回避できますが、ロックを時期尚早に解放する可能性があります。
SQLにも同じ問題があります。コードが既にトランザクション内にある場合、新しいトランザクションを開始すると、現在のトランザクションをコミットして新しいトランザクションを開始するという、予期しない残念な結果が生じます。
Javaは、ロックのネスト構造のサポートを実装することで、以下でテストするものと同様のネストの問題を解決します。Javaでは関数を「synchronized」としてマークでき、関数は開始前にロックの取得を待ち、不要になったらロックを解放するようになります。同じクラス内のsynchronized関数が別のsynchronized関数を呼び出すと、Javaはロックのネストを追跡します。外部の関数がロックを取得し、内部の関数はロック操作を実行せず、外部の関数は戻るときにロックを解放します。
PHPでは関数を「transactional」と宣言することはできませんが、コンストラクタとデストラクタを持つオブジェクトを使用してJavaのネストロジックをエミュレートできます。関数は、最初(またはほぼ最初)の操作として「$transaction = $connection->startTransaction()」を呼び出すだけで、自身をtransactionalにします。transactional関数が別の関数を呼び出すと、トランザクション抽象化レイヤーは、内側のネストレベルでtransactional操作(データベースが見る限り)を実行せずに、それらをネストします。
新しいトランザクションを開始するには、自分のコードで$transaction = $connection->startTransaction();を呼び出すだけです。$transaction変数がスコープ内にある限り、トランザクションは開いたままになります。$transactionが破棄されると、トランザクションはコミットされます。トランザクションが別のトランザクション内にネストされている場合、Drupalは各トランザクションを追跡し、最後のトランザクションオブジェクトがスコープ外になる、つまり関連するすべてのクエリが正常に完了したときだけ、最も外側のトランザクションをコミットします。
例のように、$connection->startTransaction();の戻り値を変数に代入する必要があります。戻り値を変数に代入せずにメソッドを呼び出すと、トランザクションは即座にコミットされ、役に立たなくなります。
トランザクションのロールバックは、Drupal 8では接続オブジェクト($connection->rollBack())によって管理されますが、通常はトランザクションのラッパーメソッド($action->rollBack())を使用して実行する必要があります。rollBack()トランザクションメソッドを使用する必要がある理由は、トランザクションをその名前でロールバックするためです。$connection->rollBack()はデフォルトでdrupal_transactionという名前を使用し、ネストされたトランザクションで使用すると望ましくない結果になる可能性があります。
例:
function my_transaction_function() {
// The transaction opens here.
$transaction = $connection->startTransaction();
try {
$id = $connection->insert('example')
->fields([
'field1' => 'mystring',
'field2' => 5,
])
->execute();
my_other_function($id);
return $id;
}
catch (Exception $e) {
$transaction->rollBack();
watchdog_exception('my_type', $e);
}
// You can let $transaction go out of scope here and the transaction will
// automatically be committed if it wasn't already rolled back.
// However, if you have more work to do, you will want to commit the transaction
// yourself, like this:
$transaction->commit();
// More code here that is outside of the transaction.
}
function my_other_function($id) {
// The transaction is still open here.
if ($id % 2 == 0) {
$connection->update('example')
->condition('id', $id)
->fields(['field2' => 10])
->execute();
}
}