7. Databasetoegang met PDO

PHP kent verscheidene manieren om een connectie met een database te leggen.
Vele jaren werden de mysql_* functies gebruikt, maar daarin bestaan geen voorzieningen voor moderne SQL database concepten zoals prepared statements, stored procedures of transactions. Bovendien geven de programmeurs er niet veel aandacht meer aan en doen ze dus geen moeite meer om de code up to date te houden.

PDO of PHP Data Objects is een PHP-extensie om databaseverbindingen te standaardiseren via een uniforme interface. Zo kan je met (zowat) dezelfde code vele verschillende databases aanspreken op verschillende platformen (Windows, Linux, ...).

In Xampp zit het programma PHPMyAdmin als DBMS en MySQL als databank.
Tip: maak een nieuwe database best met collatie latin1_general_ci

7.1. Basic PDO-instructies

Heb je een databank met minstens één tabel in?
Dan kunnen we een verbinding leggen met de databank en kijken hoeveel records die tabel bevat.

Daarvoor gebruiken we onderstaand PHP-script:
<?php
try {
  $db = new PDO('mysql:host=localhost;dbname=db-Naam;charset=UTF8', 'username', 'password');
  $db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
  $stmt = $db->query('SELECT * FROM tabelNaam');
  echo $stmt->rowcount();
}
catch (PDOException $ex) }
  echo "<p>FOUT: ". $ex->getMessage() . "</p>";
}
?>

Enkele opmerkingen

7.2. Gegevens toevoegen aan een tabel

Normaal zal de gebruiker de nieuwe gegevens in een HTML-formulier intypen.
Met de superglobal $_POST['inputName'] kan je dan die gegevens in variabelen in een PHP-script inlezen.

En dan kan je ze in de database opslaan:
$sql = "INSERT INTO tabelNaam (veld1, veld2, enz.) VALUES ('$var1','$var2', enz.)";
try {
  $db = new PDO('mysql:host=localhost;dbname=db-Naam;charset=UTF8','username','password');
  $db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
  $stmt = $db->exec($sql);
  echo "<p>1 record toegevoegd</p>"; //optioneel
}
catch (PDOException $ex) }
  echo "<p>FOUT: ". $ex->getMessage() . "</p>";
}
?>

Bij PDO is het niet meer nodig om addslashes() te gebruiken om speciale tekens in een veld juist op te slaan.
trim() om overbodige spaties weg te halen is echter nog altijd nuttig.

7.3. Gegevens uit een database tonen

Je gebruikt een correct SQL SELECT-commando om gegevens uit een database te selecteren.
In PHP worden die opgevangen in een array van records.

Je vraagt één rij of record uit een query op met fetch() of alle rijen met fetchAll() en tussen de haakjes komt de manier van ophalen, zoals:

Er bestaan nog meer mogelijkheden; een volledig overzicht vind je in de PHP manual.

Het verschil tussen gebruik van fetch() en fetchAll() is er eentje van prestatie (performance): fetchAll is soms ietsje sneller maar gebruikt meer geheugen.

7.4. Prepared statements

Een prepared statement is een query met parameters, variabelen, die eerst klaar gemaakt wordt – prepare() – en later uitgevoerd met execute().
Je kan zo'n prepared statement telkens opnieuw uitvoeren.

En zeker niet onbelangrijk: omdat de query en de parameters apart doorgestuurd worden naar de server, kan er geen "SQL injection" gebeuren, d.w.z. kan er geen kwaadwillige code in het SQL-commando ingevoerd worden.

Methode 1:
$stmt = $db->prepare("SELECT * FROM table WHERE id=? AND name=?");
$stmt->execute(array($id, $name));
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);

Methode 2: bindValue()
Via bindValue() geef je elke parameter met het juiste type door.
$stmt = $db->prepare("SELECT * FROM table WHERE id=? AND name=?");
$stmt->bindValue(1, $id, PDO::PARAM_INT);
$stmt->bindValue(2, $name, PDO::PARAM_STR);
$stmt->execute();
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);

Methode 3: named placeholders
Als je veel parameters hebt, kunnen die vraagtekens wel eens voor verwarring zorgen.
Dan vervang je elk vraagteken door een benoemde plaatshouder.
$stmt = $db->prepare("SELECT * FROM table WHERE id=:id AND name=:name");
$stmt->bindValue(':id', $id, PDO::PARAM_INT);
$stmt->bindValue(':name', $name, PDO::PARAM_STR);
$stmt->execute();
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);

Op dezelfde manier kan je INSERT, DELETE en UPDATE commando’s met prepare() en execute() naar de server doorsturen.

Methode 4: bindParam()

In een lus is een prepared statement bijzonder handig: je maakt het één keer en roept het telkens weer op, met een andere waarde voor de parameter(s).

Je gebruikt dan wel bindParam() in plaats van bindValue(), omdat bindParam() de variabele zelf bindt en bindValue() enkel de waarde op dat moment.
Als de waarde van de variabele verandert, zal bindParam() de nieuwe waarde gebruiken, bindValue() niet.
Voorbeeld:
$values = array('bob', 'alice', 'lisa', 'john');
$name = '';
$stmt = $db->prepare("INSERT INTO table('name') VALUES(:name)");
$stmt->bindParam(':name', $name, PDO::PARAM_STR);
foreach($values as $name) {
  $stmt->execute();
}

7.5. Transactions

Je kan verscheidene query’s in een "transactie" verzamelen en na elkaar laten uitvoeren.
Een algemeen voorbeeld:

<?php
try {
  $db->beginTransaction();
  $db->exec("SOME QUERY");
  $stmt = $db->prepare("SOME OTHER QUERY?");
  $stmt->execute(array($value));
  $stmt = $db->prepare("YET ANOTHER QUERY??");
  $stmt->execute(array($value2, $value3));
  $db->commit();
} catch(PDOException $ex) {
  //iets ging verkeerd, dus rollback!
  $db->rollBack();
  echo $ex->getMessage();
}
?>

7.6. Oefening

We zullen een dynamische website maken voor (fictieve) groothandel Jumbo.

Download de basisbestanden en pak ze uit in een submap onder je lokale server.

Start je DBMS (bv: PHPMyAdmin) en maak de database.

Vervolgens kan je artikels.sql importeren in de database.
Dat bestandje zit bij de basisbestanden. Het zijn 300 artikels.

Daarna kan je beginnen aan de oefening.