PHP PDO is de veiligste en meest flexibele manier om in moderne PHP-applicaties met een database te praten. Correct gebruikt elimineert het vrijwel volledig SQL-injectie, een van de meest voorkomende en gevaarlijkste aanvallen op webapplicaties. In deze gids leer je hoe je een PDO-verbinding opent, hoe prepared statements echt werken en welke praktische patronen je dagelijks zult gebruiken.
Waarom is SQL-injectie zo gevaarlijk?
SQL-injectie ontstaat wanneer door de gebruiker aangeleverde data rechtstreeks in een SQL-query wordt geplakt. In plaats van een normale waarde stuurt een aanvaller tekst die de betekenis van de query verandert. Hier is het klassieke, foute voorbeeld:
// DOE DIT NOOIT
$email = $_POST['email'];
$sql = "SELECT * FROM users WHERE email = '$email'";
$result = $pdo->query($sql);
Als de gebruiker ' OR '1'='1 in het veld email typt, is de voorwaarde altijd waar en kunnen alle gebruikersrecords uitlekken. Erger nog, een aanvaller zou tabellen kunnen verwijderen of zijn rechten kunnen verhogen. De oplossing is eenvoudig: meng data nooit rechtstreeks in de SQL-tekst.
De PDO-verbinding correct opzetten
Een goede PDO-verbinding bevat een paar instellingen die voorkomen dat fouten stilletjes worden ingeslikt en die het gedrag voorspelbaar maken. Wikkel de verbinding altijd in een try/catch-blok en lees de inloggegevens uit omgevingsvariabelen in plaats van ze hard te coderen.
$dsn = 'mysql:host=127.0.0.1;dbname=app;charset=utf8mb4';
$options = [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
PDO::ATTR_EMULATE_PREPARES => false,
];
try {
$pdo = new PDO($dsn, $user, $pass, $options);
} catch (PDOException $e) {
// Log de fout, toon de ruwe melding nooit aan de gebruiker
error_log($e->getMessage());
exit('Kan geen verbinding maken met de database.');
}
Drie instellingen zijn cruciaal: ERRMODE_EXCEPTION werpt fouten als excepties in plaats van stil te falen, FETCH_ASSOC geeft leesbare associatieve arrays terug, en EMULATE_PREPARES = false laat de driver echte prepared statements gebruiken, wat beter is voor zowel veiligheid als typecorrectheid.
Hoe prepared statements werken
Een prepared statement scheidt de structuur van een query van zijn data. Eerst bereid je het querysjabloon voor met placeholders, daarna stuur je de waarden apart. Hierdoor wordt de data, hoe gevaarlijk ze er ook uitziet, nooit als SQL-commando geïnterpreteerd; ze wordt altijd als waarde behandeld.
$stmt = $pdo->prepare('SELECT id, name FROM users WHERE email = ?');
$stmt->execute([$email]);
$user = $stmt->fetch();
Hier is ? een placeholder. De array die je aan execute() meegeeft, wordt veilig aan de placeholders gebonden. De driver behandelt de waarde strikt als data en de kans op injectie verdwijnt.
Benoemde parameters
Wanneer een query meerdere parameters heeft, maken benoemde placeholders de code veel leesbaarder dan positionele ?-tekens. Benoemde parameters beginnen met een dubbele punt:
$stmt = $pdo->prepare(
'INSERT INTO posts (title, body, user_id) VALUES (:title, :body, :user_id)'
);
$stmt->execute([
':title' => $title,
':body' => $body,
':user_id' => $userId,
]);
Wil je meer controle, dan kun je met bindValue() het type van elke parameter instellen:
$stmt = $pdo->prepare('SELECT * FROM products WHERE stock >= :min');
$stmt->bindValue(':min', $min, PDO::PARAM_INT);
$stmt->execute();
De resultaten lezen
PDO biedt meerdere manieren om resultaten te lezen. Gebruik fetch() voor een enkele rij en fetchAll() voor alle rijen. Bij het verwerken van veel rijen is de geheugenvriendelijke aanpak om ze één voor één in een lus op te halen:
$stmt = $pdo->prepare('SELECT id, title FROM posts WHERE user_id = ?');
$stmt->execute([$userId]);
foreach ($stmt as $row) {
echo htmlspecialchars($row['title']) . "\n";
}
Let op: gebruik htmlspecialchars() wanneer je data afdrukt die uit de database komt. PDO beschermt je tegen SQL-injectie, maar XSS (cross-site scripting) is een aparte zorg die aan de uitvoerkant moet worden afgehandeld.
Veelgemaakte fouten
- Een tabel- of kolomnaam als parameter behandelen. Placeholders zijn alleen voor waarden;
ORDER BY ?werkt niet. Valideer kolom- en tabelnamen tegen een vaste whitelist. - EMULATE_PREPARES aan laten staan. Wanneer ingeschakeld, stelt PDO de query aan zijn eigen kant samen; dat is in de meeste gevallen veilig, maar echte prepared statements zijn altijd robuuster.
- Fouten aan de gebruiker tonen. Ruwe foutmeldingen onthullen de structuur van je database. Log ze en geef een algemene melding terug.
- Dezelfde query opnieuw voorbereiden in een lus. Roep
prepare()één keer aan en alleenexecute()in de lus.
Transacties
Wanneer je meerdere onderling afhankelijke schrijfbewerkingen uitvoert, gebruik dan een transactie zodat ze allemaal slagen of allemaal samen mislukken. Als één stap mislukt, maakt rollBack() alles ongedaan:
try {
$pdo->beginTransaction();
$pdo->prepare('UPDATE accounts SET balance = balance - ? WHERE id = ?')
->execute([$amount, $fromId]);
$pdo->prepare('UPDATE accounts SET balance = balance + ? WHERE id = ?')
->execute([$amount, $toId]);
$pdo->commit();
} catch (PDOException $e) {
$pdo->rollBack();
throw $e;
}
Veelgestelde vragen
Moet ik PDO of MySQLi gebruiken?
Beide ondersteunen prepared statements en zijn veilig. Het voordeel van PDO is dat het een tiental verschillende databasedrivers via één interface ondersteunt en een leesbaardere API biedt, inclusief benoemde parameters. Voor de meeste nieuwe projecten is PDO de flexibelere keuze.
Schaden prepared statements de prestaties?
Nee, meestal het tegenovergestelde. Wanneer dezelfde query herhaaldelijk met verschillende waarden draait, kan de database zijn queryplan hergebruiken. Voor eenmalige query's is het verschil verwaarloosbaar, en de veiligheid die je wint is veel meer waard dan die kosten.
Zijn prepared statements alleen genoeg?
Tegen SQL-injectie wel, mits correct gebruikt zijn ze genoeg. Maar veiligheid is holistisch: je hebt ook XSS-bescherming op de uitvoer, invoervalidatie, autorisatie en het principe van minimale rechten nodig.
Neem databaseveiligheid serieus. Wil je dat ik een bestaand PHP-project nakijk of een veilige datalaag voor je bouw, neem dan contact op en laten we samen een solide basis leggen.