Transacties
Drupal ondersteunt ook transacties, inclusief een transparant fallback-mechanisme voor databases die geen transacties ondersteunen. Transacties kunnen echter vrij complex zijn als je probeert twee transacties tegelijkertijd te starten. Het gedrag in dat geval hangt ook af van de gebruikte database.
Een soortgelijk probleem bestaat bij geneste locks in C / C++. Als de code lock A al heeft en opnieuw probeert lock A te verkrijgen, raakt de code geblokkeerd. Als je code schrijft die controleert of het al een lock heeft en het niet opnieuw probeert te verkrijgen, vermijd je een deadlock, maar je kunt de lock voortijdig vrijgeven.
In SQL hebben we hetzelfde probleem. Als je code zich al in een transactie bevindt, heeft het starten van een nieuwe transactie het onverwachte en trieste gevolg dat de huidige transactie wordt gecommit en er een nieuwe wordt gestart.
Java lost het probleem van geneste locks op door ondersteuning te implementeren voor een geneste structuur, vergelijkbaar met wat we hieronder testen. Java staat toe functies te markeren als "synchronized", wat ervoor zorgt dat de functie wacht tot het lock is verkregen voordat deze wordt uitgevoerd, en het lock vrijgeeft wanneer het niet meer nodig is. Als een gesynchroniseerde functie een andere gesynchroniseerde functie binnen dezelfde klasse aanroept, houdt Java de geneste lock bij. De buitenste functie verkrijgt het lock, de binnenste functie voert geen lock-operaties uit, en de buitenste functie geeft het lock vrij bij terugkeer.
Hoewel we in PHP geen functies als "transactional" kunnen declareren, kunnen we de geneste logica van Java emuleren met objecten met constructors en destructors. Een functie roept gewoon $transaction = $connection->startTransaction() aan als eerste (of bijna eerste) operatie om zichzelf transactional te maken. Als een transactional functie een andere aanroept, nestelt onze transactiesabstractie ze, zonder dat er (zichtbare voor de database) transactionele operaties plaatsvinden binnen de binnenste niveaus.
Om een nieuwe transactie te starten, roep je gewoon $transaction = $connection->startTransaction(); aan in je eigen code. De transactie blijft open zolang de variabele $transaction in scope blijft. Wanneer $transaction wordt vernietigd, wordt de transactie gecommit. Als je transactie genest is binnen een andere, houdt Drupal elke transactie bij en commit de meest buitenste transactie pas wanneer het laatste transactieobject buiten scope gaat, dat wil zeggen wanneer alle bijbehorende queries succesvol zijn voltooid.
Je moet de returnwaarde van $connection->startTransaction(); toewijzen aan een variabele, zoals in het voorbeeld. Als je de methode aanroept zonder de returnwaarde aan een variabele toe te wijzen, wordt je transactie onmiddellijk gecommit, waardoor ze nutteloos wordt.
Het terugdraaien van een transactie wordt in Drupal 8 beheerd door het connectieobject ($connection->rollBack()), maar doorgaans moet dit gebeuren met behulp van de transactie-wrappermethode ($action->rollBack()). De reden om dit via de methode rollBack() van de transactie te doen, is dat deze die specifieke transactie terugdraait op basis van zijn naam, terwijl $connection->rollBack() standaard de naam drupal_transaction gebruikt en ongewenste resultaten kan geven bij gebruik met geneste transacties.
Voorbeeld:
function my_transaction_function() {
// De transactie wordt hier geopend.
$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);
}
// Je kunt $transaction hier uit scope laten gaan en de transactie
// wordt automatisch gecommit als ze nog niet is teruggedraaid.
// Maar als je nog meer werk hebt te doen, wil je de transactie
// zelf committen, zoals dit:
$transaction->commit();
// Meer code hier die buiten de transactie valt.
}
function my_other_function($id) {
// De transactie is hier nog steeds open.
if ($id % 2 == 0) {
$connection->update('example')
->condition('id', $id)
->fields(['field2' => 10])
->execute();
}
}