Hoofdstuk 6 PHP Data Objects _________________________________________________________________
Cursus PHP & MySQL Groep D
Hoofdstuk 1: PHP Data Objects 1.1 Inleiding Tegenwoordig kun je er bij het ontwikkelen van een website bijna niet meer omheen: het gebruik van een database. Of je nu bezig bent met een persoonlijke website met gastenboek, een site voor je bedrijf met support forum of met een ingewikkelde webshop; de basis van elke goede website wordt gevormd door een database.
Er zijn vrij veel verschillende database systemen die je voor dit doeleinde kunt gebruiken en allemaal kennen ze hun eigen functies. Aangezien elke database anders is, vereist het werken ermee in PHP steeds een andere aanpak. Voor de meest gebruikte database systemen waaronder MySQL, PostgreSQL en SQLite, biedt PHP standaard ondersteuning. Echter zul je voor het benaderen van deze verschillende databases altijd nog verschillende functies gebruiken.
Met de komst van PHP Data Objects (PDO) in PHP 5.1 hebben we de beschikking over een uiterst handige extensie voor het benaderen van verschillende databases. Naast het gebruik van een object georiënteerde notatie biedt deze extensie ook nog andere voordelen. Het wordt namelijk een stuk eenvoudiger om resultaat sets te verwerken, prepared statements uit te voeren en transactions te gebruiken.
Nota: Vanaf PHP6 zullen de standaard database functies (mysql_*, pg_*, etc) niet standaard meer ondersteund worden. Hoewel ze waarschijnlijk nog lange tijd als extensie beschikbaar blijven, is het verstandig om nu al naar alternatieven te kijken. Vooral als je een nieuw systeem aan het ontwikkelen bent is het gebruik van PDO zeker een aanrader.
In dit hoofdstuk van de cursus zullen we dieper op de werking van de PDOextensie ingaan, en dit aan de hand van verschillende voorbeelden met betrekking tot de belangrijkste functies. Voor een allesomvattende handleiding over PDO verwijzen we je graag door naar php.net.
1.2 Gebruik van de PDO-extensie Voor het gebruik van de PDO extensie moeten we natuurlijk wel eerst weten of de database die we willen gebruiken wel ondersteund wordt. Om PDO met een bepaalde database te gebruiken, moeten we de juiste PDO-driver geïnstalleerd hebben.
Controleren welke databasedrivers geïnstalleerd zijn
”; print_r(PDO::getAvailableDrivers()); echo “”; ?> Dat levert dan het volgende resultaat op. Array ( [0] => sqlite2 [1] => pgsql [2] => mysql ) Dit betekent dat we op dit moment de juiste drivers geïnstalleerd hebben om te werken met de databasesystemen MySQL, PostgreSQL en SQLite. Er worden echter nog een boel andere databasesystemen ondersteund, zoals je uit onderstaand lijstje kunt opmaken.
MySQL
PostgreSQL
SQLite
Microsoft SQL Server
ODBC
Oracle
Daar wij doorheen heel onze cursus met MySQL zullen werken, laten we de andere
systemen
buiten
beschouwing.
Voor
meer
informatie
over
de
samenwerking tussen PDO en de andere systemen verwijzen we je opnieuw door naar php.net.
2.2.1 Verbinden met een Database Het verbinden met een database met behulp van PDO is kinderlijk eenvoudig. Het komt eigenlijk er op neer dat je een nieuwe instantie van de PDO klasse gaat creëren. Natuurlijk moet je bij het instantiëren van de klasse wel de juiste parameters meegeven. Hieronder zie je hoe dat in z’n werk gaat voor een verbinding met een MySQL database. Dat je voor het opzetten van een verbinding met elke willekeurige database telkens dezelfde regel PHP kunt gebruiken, heeft te maken met het feit dat het eigenlijke verbinden met de database achter de schermen afgehandeld wordt. De meegegeven parameters worden door de extensie geïnterpreteerd en een verbinding met de juiste database wordt gemaakt.
2.2.2 Sluiten van een Verbinding Het sluiten van een verbinding is al even eenvoudig. Het enige dat je hier dient te doen, is de instantie van de PDO-klasse vernietigen. Dat ziet er dan als volgt uit. Het handmatig sluiten van een databaseverbinding is in principe niet nodig. Aangezien het bestaan van de verbinding direct afhangt van het bestaan van de instantie van de PDO klasse, zal de verbinding verbroken worden zodra een script eindigt. Het veelvuldig tussendoor sluiten en openen van verbindingen kan tot een zeker performance verlies leiden, zeker als je verbinding maakt met een externe database server.
1.3 Uitvoeren van Queries Nu we weten hoe we verbinding maken met een database, kunnen we verder gaan met het uitvoeren van queries. De PDO klasse heeft hiervoor twee methodes beschikbaar, genaamd query() en exec().
Er zit een klein verschil in werking van deze twee methodes. De eerste, query(), voert enkel de query uit en geeft een resultaat terug. De tweede daarentegen voert de query uit en geeft daarnaast het aantal aangepaste rijen van een INSERT, UPDATE of DELETE query terug.
Een SELECT query
query($sql); foreach($results as $row) { echo $row['naam'] . ”
”; } ?> Allereerst maken we een nieuwe verbinding door een nieuwe instantie van de PDO klasse aan te maken. Vervolgens voeren we met de query() methode een SQL query uit. De resultaat set kunnen we daarna uitlezen met een for each loop.
Een UPDATE query
exec($sql); echo 'Aantal aangepaste rijen: '.$affected_rows; ?>
Nu voeren we de query uit met de exec() methode die als resultaat dus een aantal aangepaste rijen teruggeeft. Op deze manier kun je dus direct controleren of er wel wat gewijzigd is.
Een INSERT query
exec($sql); $insert_id = $db->lastInsertId(); echo “ID van laatst ingevoegde record: “ . $insert_id; ?> Vanzelfsprekend ziet een INSERT query er precies hetzelfde uit. Vaak wil je na een INSERT query het id van het laatst ingevoegde record weten. Met PDO kun je dat achterhalen met de lastInsertId() methode zoals te zien is in het voorbeeld.
Natuurlijk moeten we niet vergeten om de in te voegen variabelen te beveiligen om
SQL-injectie
te
voorkomen.
Waar
we
normaal
altijd
mysql_real_escape_string() zouden gebruiken, kunnen we nu de quote() methode van de PDO klasse gebruiken. quote($_POST['naam']); $sql = " INSERT INTO tabel (naam) VALUES ('".$naam."') "; $db->exec($sql); ?>
1.4 Foutafhandelingen In de voorbeelden op de vorige pagina hebben we nog geen enkele vorm van foutafhandeling toegepast, maar natuurlijk mag dat niet vergeten worden. Foutafhandeling in een PHP-script is een van de belangrijkste dingen die er zijn. PDO biedt drie verschillende manieren van foutafhandeling. 1. SILENT - Fouten worden onderdrukt en zullen handmatig opgevraagd moeten worden. 2. WARNING - Gebruikt de error_handler van PHP en geeft een E_WARNING. 3. EXCEPTION - Gebruikt PDOException klasse. Van dit lijstje is SILENT de standaard instelling. Dat betekent dat je altijd de code en informatie van de foutmelding met de methodes errorInfo() en errorCode() zult moeten ophalen om te controleren of er fouten opgetreden zijn. Een uitzondering op deze regel is een fout bij het verbinden met de database, oftewel het instantiëren van de PDO klasse. In dat geval wordt er een PDOException object aangemaakt dat op te vangen is met een try/catch combinatie. Dit staat beschreven in onderstaand voorbeeld. getMessage(); } ?> Dit zal de volgende melding geven: SQLSTATE[42000] [1049] Unknown database 'bestaat_niet' Zoals we zien krijgen we netjes een melding dat de opgegevens database niet bestaat. Het is belangrijk om steeds op deze manier te controleren of een verbinding gelukt is. Mocht er namelijk iets fout gaan en de fout wordt niet afgehandeld, dan zal PHP een backtrace weergeven waarin mogelijk alle details van de databaseverbinding
staan.
Dit
omvat
dus
gebruikersnaam en wachtwoord van je database!
mogelijk
ook
gegevens
als
Als we met de standaard instelling – SILENT – een fout maken in een query die we uitvoeren, krijgen we daar geen foutmelding van te zien. Bijvoorbeeld: query($sql); foreach($results as $row) { echo $row['naam'] . “
”; } } catch(PDOException $e) { echo $e->getMessage(); } ?> De enige melding die we nu krijgen is: Warning: Invalid argument supplied for foreach() We zien dat $results dus geen geldig argument voor de foreach() is en dus doet dat ons vermoeden dat er iets fout gegaan is met de query. Maar als dat zo is, zouden we daar graag een foutmelding van krijgen. In dat geval zouden we ervoor kunnen kiezen om de WARNING instelling te gebruiken. Dan wordt er naast het aanmaken van een error code ook een standaard E_WARNING melding gegeven. Het type foutafhandeling kunnen we instellen door het type toe te kennen aan het attribuut dat de foutafhandeling bepaald. Dit doen we met de setAttribute() methode. Op de volgende pagina vind je een voorbeeld van foutafhandeling met een WARNING.
setAttribute(PDO::ATTR_ERRMODE, PDO::ERR MODE_WARNING); $sql = "SELECT naam FROM bestaat_niet";
$results = $db->query($sql); foreach($results as $row) { echo $row['naam'] . “
”; } } catch(PDOException $e) { echo $e->getMessage(); } ?> De foutmelding die nu gegeven wordt: Warning: PDO::query() [function.PDOquery]: SQLSTATE[42S02]: Base table or view not found: 1146 Table 'test.bestaat_niet' doesn’t exist. Warning: Invalid argument supplied for foreach(). We zien dat er zowel een foutmelding gegeven wordt op het punt waar de query uitgevoerd wordt als op de plaats van de foreach(). Zoals je altijd te werk gaat met debuggen, los je nu ook eerst de eerste fout op. Dit zal in dit geval ook de tweede foutmelding laten verdwijnen. Als laatste instelling hebben we EXCEPTION over. In dat geval zal er net zoals in het geval van de fout met de verbinding, voor de fout een PDOException object aangemaakt worden. De gegevens van deze foutmelding kunnen we wederom uitlezen in het catch gedeelte van het script.
setAttribute(PDO::ATTR_ERRMODE, PDO::ERR MODE_EXCEPTION); $sql = "SELECT naam FROM bestaat_niet"; $results = $db->query($sql); foreach($results as $row) { echo $row['naam'] . “
”; } } catch(PDOException $e) { echo $e->getMessage(); } ?> De volgende foutmelding werd gegenereerd: SQLSTATE[42S02]: Base table or view not found: 1146 Table 'test. bestaat_niet' doesn't exist. We zien dat we hier alleen een foutmelding op de query te zien krijgen en geen verdere meldingen dat bijvoorbeeld het argument van de foreach() niet juist is. Zoals gewoonlijk kunnen we natuurlijk het hele PDOException object uitkleden en alle informatie over een fout naar boven halen, dit staat beschreven in het voorbeeld op de volgende pagina.
setAttribute(PDO::ATTR_ERRMODE, PDO::ERR MODE_EXCEPTION); $sql = "SELECT naam FROM bestaat_niet"; $results = $db->query($sql); foreach($results as $row) { echo $row['naam'] . “
”; } } catch(PDOException $e) { echo “<pre>”; echo “Regelnummer: “ . $e->getLine() . “
”; echo “Bestand: “ . $e->getFile() . “
”; echo “Foutmelding: “ . $e->getMessage() . “
”; echo “”; } ?> De enige melding die we nu krijgen is: Regelnummer: 8 Bestand: Dummy File Path Foutmelding: SQLSTATE[42S02]: Base table or view not found: 11 46 Table 'test.bestaat_niet' doesn't exist Wij zullen steeds de Exception gebruiken als foutafhandeling. Je gebruikt de try/catch syntax immers toch al om te controleren of de verbinding gelukt is. Daarnaast krijg je alle opvolgende foutmeldingen die het gevolg zijn van een foute query niet te zien en kun je helemaal zelf bepalen welke informatie je wel wilt tonen.
1.5 Prepared Statements Op dit punt zul je misschien denken dat de PDO-extensie niet veel meer biedt dan de mogelijkheid om te verbinden met meerdere database systemen en het uitvoeren van databasegerelateerde taken. Dat is zeker niet het geval aangezien PDO met een aantal mooie features komt, waaronder het werken met prepared statements. Deze prepared statements bieden de programmeur de mogelijkheid tot het creëren van queries die veiliger zijn, waarvan de performance beter is en die eenvoudiger te noteren zijn. Het grote voordeel van prepared statements is dat een bepaalde query op een efficiënte manier vele malen achter elkaar uitgevoerd kan worden. Bij het gebruik van prepared statements wordt er allereerst een template van de uit te voeren query naar de server gestuurd. Deze wordt door de server gecontroleerd op syntax fouten en uitgevoerd om te controleren of de query logisch is. Vervolgens wordt deze opgeslagen in een speciale buffer. Op het moment dat de query daadwerkelijk uitgevoerd moet worden, stuur je de in de query in te vullen gegevens naar de database. Vervolgens wordt een complete query gevormd en uitgevoerd. Op de volgende pagina vind je een voorbeeld van een prepared INSERT statement.
setAttribute(PDO::ATTR_ERRMODE, PDO::ERR MODE_EXCEPTION); $sql = " INSERT INTO tabel (naam) VALUES (:naam) “; $stmt = $db->prepare($sql); $stmt-> bindParam(':naam', $naam, PDO::PARAM_STR); $naam = “Nick”; $stmt->execute(); } catch(PDOException $e) { echo “<pre>”; echo “Regel: “ . $e->getLine() . “
”; echo “Bestand: “ . $e->getFile() . “
”; echo “Foutmelding: “ . $e->getMessage(); echo “”; } ?> In dit script hebben we eerst een template gemaakt van de INSERT query die we willen gebruiken. Het stukje ':naam' in de template is een zogenaamde parameter marker of placeholder en geeft de plaats aan waar straks een variabele ingevuld gaat worden. Vervolgens roepen we de prepare() methode aan om de query naar de server te sturen en te laten controleren. Als dat gelukt is, gebruiken we de bindParam() methode. Met deze methode koppelen we een bepaalde variabele aan een bepaalde placeholder. Op die manier weet PHP straks welke variabele hij onder welke naam naar de server moet sturen zodat deze het begrijpt. De optionele derde parameter van deze methode geeft aan als wat voor type de variabele behandeld moet worden, in dit geval dus een string. Nadat we de variabele vervolgens een waarde hebben gegeven, sturen we alles wederom naar de server met de execute() methode. Als alles goed is gegaan krijgen we geen foutmeldingen en zal er een nieuw record ingevoegd zijn. Natuurlijk is het ook mogelijk om meerdere placeholders te gebruiken om meerdere gegevens in te voeren. En daarnaast kunnen we ook nog eens meerdere queries achter elkaar uitvoeren.
Hieronder vind je een voorbeeld van een prepared statement met meerdere gegevens en meerdere queries. setAttribute(PDO::ATTR_ERRMODE, PDO::ERR MODE_EXCEPTION); $personen = array( array( 'naam' => 'Martijn', 'email' => '
[email protected]', 'leeftijd' => 19 ), array( 'naam' => 'Joris', 'email' => '
[email protected]', 'leeftijd' => 20 ), array( 'naam' => 'Maarten', 'email' =>
[email protected]', 'leeftijd' => 18 ) ); $sql = " INSERT INTO tabel (naam, leeftijd, email) VALUES (:naam, :leeftijd, :email) "; $stmt = $db->prepare($sql); $stmt-> bindParam(':naam', $naam, PDO::PARAM_STR); $stmt-> bindParam(':email', $email, PDO::PARAM_STR); $stmt-> bindParam(':leeftijd', $leeftijd, PDO::PARAM_INT); foreach($personen as $persoon) { $naam = $persoon['naam']; $email = $persoon['email']; $leeftijd = $persoon['leeftijd']; $stmt->execute(); } } catch(PDOException $e) { echo “<pre>”; echo “Regel: “ . $e->getLine() . “
”; echo “Bestand: “ . $e->getFile(). “
”; echo “Foutmelding: “ . $e->getMessage(); echo “”; } ?>
Voor elke placeholder die we in de query template aangegeven hebben, roepen we de bindParam() methode aan om een variabele te koppelen.
We lezen de array met gegevens uit met een for each loop, geven de gekoppelde variabelen een waarde en voeren de statement uit met de execute() methode. Je ziet dat er geen gebruik gemaakt wordt van de quote() methode om in te voegen strings te beveiligen. Dit is niet nodig aangezien de execute() methode al zorgt voor een veilige afhandeling van de gegevens. Daarnaast is er nog een andere manier waarop we de prepared statement uit kunnen voeren. We kunnen alle gegevens namelijk ook in een array als parameter meegeven aan de execute() methode van het statement. Hieronder zie je hoe we statements met een gegevensarray als parameter zouden kunnen uitvoeren. setAttribute(PDO::ATTR_ERRMODE, PDO::ERR MODE_EXCEPTION); $personen = array( array( ':naam' => 'Martijn', ':email' => '
[email protected]', ':leeftijd' => 19 ), array( ':naam' => 'Joris', ':email' => '
[email protected]', ':leeftijd' => 20 ), array( ':naam' => 'Maarten', ':email' => '
[email protected]', ':leeftijd' => 18 ) ); $sql = " INSERT INTO tabel (naam, leeftijd, email) VALUES (:naam, :leeftijd, :email) "; $stmt = $db->prepare($sql);
foreach($personen as $persoon) { $stmt->execute($persoon); } } catch(PDOException $e) { echo “<pre>”; echo “Regel: “ . $e->getLine() . “
”; echo “Bestand: “ . $e->getFile() . “
”; echo “Foutmelding: “ . $e->getMessage(); echo ””; } ?> Op de volgende pagina vind je nog een voorbeeld, waar we een UPDATE query gaan uitvoeren met een prepared statement. Dit gelijkt erg op het INSERT voorbeeld, maar kent toch één belangrijk verschil. Bij een UPDATE of DELETE query is het namelijk altijd van belang om te controleren of er wel degelijk rijen aangepast zijn. Met de methode rowCount() kunnen we het aantal aangepaste rijen van het laatste uitgevoerde statement ophalen. Mocht dit gelijk zijn aan 0 dan creëren we een nieuw PDOException object met de betreffende melding
setAttribute(PDO::ATTR_ERRMODE, PDO::ERR MODE_EXCEPTION); $personen = array('Martijn', 'Maarten'); $sql = " UPDATE tabel SET email = 'geen mail' WHERE naam = :naam "; $stmt = $db->prepare($sql); $stmt-> bindParam(':naam', $naam, PDO::PARAM_STR); foreach($personen as $persoon) { $naam = $persoon; $stmt->execute(); if($stmt->rowCount() == 0) { throw new PDOException(“Er zijn ge en rijen gewijzigd in de UPDATE que ry met naam: “ . $naam); } } } catch(PDOException $e) { echo '<pre>'; echo 'Regel: '.$e->getLine().'
'; echo 'Bestand: '.$e->getFile().'
'; echo 'Foutmelding: '.$e->getMessage(); echo ''; } ?>
Naast het invoegen, updaten en verwijderen van records met een prepared statement, is het ook mogelijk om een SELECT query uit te voeren. Hoewel een het uitvoeren van een prepared SELECT statement veel lijkt op de voorbeelden hierboven, werkt het toch net iets anders. We hebben hier namelijk te maken met gegevens die we uit de database ophalen, dus zullen we die gegevens op een bepaalde manier zichtbaar moeten maken. Hieronder vind je een voorbeeld waarbij we de gegevens uitlezen met een fetch(). setAttribute(PDO::ATTR_ERRMODE, PDO::ERR MODE_EXCEPTION); $sql = "SELECT naam FROM tabel WHERE leeftijd > :leeftijd"; $stmt = $db->prepare($sql); $stmt-> bindParam(':leeftijd', $leeftijd, PDO::PARAM_INT); $leeftijd = 18; $stmt->execute(); while($row = $stmt->fetch(PDO::FETCH_ASSOC)) { echo $row['naam'] . “
”; } } catch(PDOException $e) { echo “<pre>”; echo “Regel: “ . $e->getLine() . “
”; echo “Bestand: “ . $e->getFile() . “
”; echo “Foutmelding: “ . $e->getMessage(); echo “”; } ?> Dit geeft als resultaat iets als: Martijn - 19 Joris - 20 Tot aan de execute() methode is dit script precies hetzelfde als voorgaande voorbeelden. Maar zoals ik al zei moeten we de resultaten van de query natuurlijk zichtbaar kunnen maken. Hier gebruiken we de fetch() methode voor.
Aan de fetch() methode kunnen we optioneel een parameter meegeven om aan te geven hoe we willen dat de rijen gefetched worden. Dit is vergelijkbaar met de optionele parameter van de mysql_fetch_array() functie. De belangrijkste beschikbare parameters zijn:
PDO::FETCH_ASSOC - Een rij als associatieve array
PDO::FETCH_NUM - Een rij als numerieke array
PDO::FETCH_BOTH - Een rij zowel associatief als numeriek te gebruiken (standaard)
In tegenstelling tot bijvoorbeeld de MySQLi extensie kent PDO geen methode om het aantal rijen in een resultaatset te bepalen. Wil je dit aantal weten, dan zul je een extra query uit moeten voeren om dat aantal te bepalen. Het makkelijkste is het om hier een query met COUNT() voor te gebruiken: zie onderstaand voorbeeld. 18"; $row = $db->query($sql)-> fetch(PDO::FETCH_ASSOC); echo $row['aantal']; // Output: 2 } catch(PDOException $e) { echo “<pre>”; echo “Regel: “ . $e->getLine() . “
”; echo “Bestand: “ . $e->getFile() . “
”; echo “Foutmelding: “ . $e->getMessage(); echo “”; } ?> In dit voorbeeld gebruiken we query() om een eenvoudige query uit te voeren en fetch() om het resultaat van de query in een array te zetten. De variabele $row['aantal'] zal in dit geval het aantal rijen in de resultaat set van de query bevatten. Verder met het fetchen van gegevens. Een tweede methode die we daarvoor kunnen gebruiken is fetchAll(), deze zullen we aan de hand van een voorbeeld verder toelichten op de volgende pagina.
setAttribute(PDO::ATTR_ERRMODE, PDO::ERR MODE_EXCEPTION); $sql = "SELECT naam FROM tabel WHERE leeftijd > :leeftijd"; $stmt = $db->prepare($sql); $stmt->bindParam(':leeftijd', $leeftijd); $leeftijd = 18; $stmt->execute(); $result = $stmt->fetchAll(); foreach($result as $row) { echo $row['naam'] . “
”; } } catch(PDOException $e) { echo “<pre>”; echo “Regel: “ . $e->getLine() . “
”; echo “Bestand: “ . $e->getFile() . “
”; echo “Foutmelding: “ . $e->getMessage(); echo “”; } ?> Wederom zien we hier hetzelfde resultaat: Martijn - 19 Joris - 20 De fetchAll() methode doet dus niets meer dan alle rijen uit de resultaat set onder een nieuwe key in een array zetten. Vervolgens kunnen we deze array met een for each loop uitlezen. Aan de fetchAll() methode kunnen we natuurlijk ook weer een parameter meegeven om aan te geven hoe de resultaten gefetched moeten worden.
1.6 Transacties Transacties klinken de doorgewinterde databasegebruiker waarschijnlijk zeer bekend in de oren, maar niet iedereen zal weten wat je met dit principe kunt. Simpel gezegd heb je bij transacties in een database de mogelijkheid om een reeks queries uit te voeren zonder dat je eventuele aanpassingen definitief doorvoert. Aan het einde van deze reeks kun je COMMIT gebruiken om de wijzigingen definitief door te voeren of ROLLBACK om alle wijzigingen terug te draaien. De PDO extensie biedt ons drie methodes voor het werken met transacties: beginTransaction(), commit() en rollBack(). De eerste methode roepen we aan het begin aan, deze zorgt ervoor dat autocommit uitgezet wordt en dat queries pas definitief doorgevoert worden zodra commit() aangeroepen wordt. De rollback() methode kunnen we gebruiken om gemaakte wijzigingen terug te draaien mocht er een fout optreden. setAttribute(PDO::ATTR_ERRMODE, PDO::ERR MODE_EXCEPTION); $db->beginTransaction(); $db->query("INSERT INTO tabel (naam) VALUES ('Martijn')"); $db->query("INSERT INTO tabel (naam) VALUES ('Nick')"); $db->query("INSERT INTO tabel (naam) VALUES ('Maarten')"); $db->query("INSERT INTO tabel (naam) VALUES ('Joris')"); $db->commit(); } catch(PDOException $e) { if(isset($db)) { $db->rollBack(); } echo '<pre>'; echo 'Regel: '.$e->getLine().'
'; echo 'Bestand: '.$e->getFile().'
'; echo 'Foutmelding: '.$e->getMessage(); echo ''; } ?>
We zien hier inderdaad de drie methodes waar ik het al eerder over had terug. Na beginTransaction() voeren we een viertal INSERT queries uit. Als alles goed gaat, voeren we de wijzigingen definitief door met commit(). Mocht er een fout optreden dan vangen we die op in het catch gedeelte van het script en draaien we alle reeds gemaakte wijzigingen terug. We moeten we even controleren of $db bestaat, aangezien dit gedeelte ook fouten in de verbinding opvangt. Als daar een fout in optreedt, zal $db niet bestaan en zal het aanroepen van rollback() niet mogelijk zijn. Hoewel ik hier nu vier losse queries gebruikt heb, kunnen we deze natuurlijk ook weer vervangen door een prepared statement. Dat zou een nettere oplossing geven. Een ander goed voorbeeld om het gebruik van transacties te verduidelijken is een geldtranscatie tussen twee bankrekeningen van respectievelijk persoon A en B. Er zal 500 euro overgemaakt worden van persoon A naar persoon B, maar persoon A mag natuurlijk geen negatief saldo krijgen. Daarnaast kan het totale saldo uit de tabel nooit groter of kleiner worden, er kan immers geen geld ontstaan of verdwijnen. Dat betekent dat de ene query niet uitgevoerd mag worden zonder dat de ander ook uitgevoerd wordt. Duidelijk iets om een transactie voor te gebruiken. Dit voorbeeld zie je uitgewerkt over de laatste pagina’s van dit hoofdstuk. setAttribute(PDO::ATTR_ERRMODE, PDO::ERR MODE_EXCEPTION); $queries[] = "UPDATE rekeningen SET saldo = saldo - 500 WHERE klant = 'A'"; $queries[] = "UPDATE rekeningen SET saldo = saldo + 500 WHERE klant = 'B'"; $controle = "SELECT saldo FROM rekeningen WHERE klant = 'A'"; $db->beginTransaction();
foreach($queries as $query) { $affected = $db->exec($query); if($affected == 0) { throw new PDOException(“Update query niet geslaagd: “ . $query); } } $result = $db->query($controle); $row = $result->fetchAll(); if($row[0]['saldo'] < 0) { throw new PDOException(“Saldo niet toereikend”); } $db->commit(); echo “<pre>” . print_r (get_class_methods($db) ,true) . “”; } catch(PDOException $e) { if(isset($db)) { $db->rollBack(); } echo “<pre>”; echo “Regel: “ . $e->getLine() . “
”; echo “Bestand: “ . $e->getFile() . “
”; echo “Foutmelding: “ . $e->getMessage(); echo “”; } ?> We beginnen met het uitvoeren van de UPDATE queries. Mocht daar een fout in optreden, dan willen we dat natuurlijk weten dus maken we in dat geval een nieuw PDOException object aan. Daarna voeren we de controle query uit en controleren het resultaat. Als dit kleiner is dan 0 heeft persoon A een negatief saldo. Omdat dit niet toegestaan is, gooien
we er
weer een foutmelding tegenaan zodat alle wijzigingen
teruggedraaid worden. Op deze manier zorgt onze transactie ervoor dat er dus nooit een query zonder de ander uitgevoerd kan worden en dat alles teruggedraaid wordt als er ergens een negatief saldo ontstaat.