Transazioni
Drupal supporta anche le transazioni, inclusa una modalitĂ di fallback trasparente per i database che non supportano le transazioni. Tuttavia, le transazioni possono diventare piuttosto complicate se si tenta di avviarne due contemporaneamente. Il comportamento in questo caso dipende anche dal database.
Un problema analogo esiste con i lock annidati in C / C++. Se il codice ha giĂ ottenuto il lock A e tenta di ottenere nuovamente il lock A, il codice rimarrĂ bloccato. Se scrivi codice che controlla se possiede giĂ un lock e non tenta di acquisirlo di nuovo, eviti una situazione di stallo, ma rischi di rilasciare il lock in modo prematuro.
In SQL abbiamo lo stesso problema. Se il tuo codice si trova giĂ in una transazione, avviarne una nuova ha la conseguenza imprevista e indesiderata di confermare la transazione corrente e avviarne una nuova.
Java risolve il problema dell’annidamento con i suoi lock implementando il supporto per una struttura di annidamento simile a quella che testiamo qui sotto. Java permette di contrassegnare le funzioni come “synchronized”, il che obbliga la funzione ad attendere il lock prima di essere eseguita e a rilasciarlo quando non serve più. Se una funzione sincronizzata chiama un’altra funzione sincronizzata nello stesso classe, Java tiene traccia dell’annidamento del lock. La funzione esterna ottiene il lock, la funzione interna non esegue operazioni di lock, e la funzione esterna rilascia il lock al termine.
Anche se non possiamo dichiarare funzioni “transactional” in PHP, possiamo emulare la logica di annidamento di Java usando oggetti con costruttori e distruttori. La funzione deve solo chiamare “$transaction = $connection->startTransaction()” come prima (o quasi prima) operazione per rendersi transazionale. Se una funzione transazionale chiama un’altra funzione transazionale, il nostro livello di astrazione delle transazioni le annida, senza eseguire operazioni transazionali (dal punto di vista del database) all’interno dei livelli interni di annidamento.
Per avviare una nuova transazione, basta chiamare $transaction = $connection->startTransaction(); nel tuo codice. La transazione rimarrà aperta finché la variabile $transaction rimane nello scope. Quando $transaction viene distrutta, la transazione sarà confermata. Se la tua transazione è annidata in un’altra, Drupal terrà traccia di ogni transazione e confermerà solo la transazione più esterna quando l’ultimo oggetto transaction uscirà dallo scope, cioè quando tutte le query associate saranno state completate con successo.
Devi assegnare il valore restituito da $connection->startTransaction(); a una variabile, come nell’esempio. Se chiami il metodo senza assegnare il valore restituito a una variabile, la tua transazione verrà confermata immediatamente, rendendola inutile.
Il rollback della transazione è gestito dall’oggetto connessione ($connection->rollBack()) in Drupal 8, ma di solito deve essere eseguito con il metodo wrapper della transazione ($action->rollBack()). La ragione per cui questo deve essere fatto con il metodo rollBack() dell’oggetto transaction è che esso effettua il rollback di quella specifica transazione in base al suo nome, mentre $connection->rollBack() imposta di default il nome drupal_transaction e può produrre risultati indesiderati se usato con transazioni annidate.
Esempio:
function my_transaction_function() {
// La transazione inizia qui.
$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);
}
// Puoi lasciare che $transaction esca dallo scope qui, e la transazione
// verrà automaticamente confermata se non è già stata annullata.
// Tuttavia, se hai altro lavoro da fare, vorrai confermare la transazione
// manualmente, come segue:
$transaction->commit();
// Altro codice qui, al di fuori della transazione.
}
function my_other_function($id) {
// La transazione è ancora aperta qui.
if ($id % 2 == 0) {
$connection->update('example')
->condition('id', $id)
->fields(['field2' => 10])
->execute();
}
}