🚀 SQL in de praktijk
Van query naar applicatie — PHP PDO, prepared statements, Doctrine ORM en wat nu?
PHP & PDO — database verbinding
PDO (PHP Data Objects) is de standaard manier om vanuit PHP met databases te praten. Het werkt met MySQL, PostgreSQL, SQLite en meer.
<?php
// Verbinding maken met MySQL
$pdo = new PDO(
'mysql:host=localhost;dbname=webshop;charset=utf8mb4',
'gebruikersnaam',
'wachtwoord',
[PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION]
);
// SQLite (voor testen — geen server nodig)
$pdo = new PDO('sqlite:database.db');
// Alle gebruikers ophalen
$stmt = $pdo->query('SELECT * FROM gebruikers');
$gebruikers = $stmt->fetchAll(PDO::FETCH_ASSOC);
foreach ($gebruikers as $gebruiker) {
echo $gebruiker['naam'] . ' — ' . $gebruiker['email'];
}
// GEVAARLIJK — gebruikersinput direct in de query!
$naam = $_GET['naam'];
$stmt = $pdo->query("SELECT * FROM gebruikers WHERE naam = '$naam'");
// Aanvaller typt: ' OR '1'='1 → ziet ALLE gebruikers
Prepared Statements — veilig queryen
Prepared statements scheiden de SQL-code van de data. De database compileert de query eerst, dan pas worden de parameters ingevuld — SQL injection is onmogelijk.
// Gebruiker zoeken op naam (gebruikersinput veilig!)
$stmt = $pdo->prepare('SELECT * FROM gebruikers WHERE naam = :naam');
$stmt->execute([':naam' => $_GET['naam']]);
$gebruiker = $stmt->fetch(PDO::FETCH_ASSOC);
// Nieuwe gebruiker toevoegen
$stmt = $pdo->prepare(
'INSERT INTO gebruikers (naam, email, leeftijd) VALUES (:naam, :email, :leeftijd)'
);
$stmt->execute([
':naam' => 'Pieter Klaas',
':email' => 'pieter@email.nl',
':leeftijd' => 27,
]);
// Positie-gebaseerde placeholders
$stmt = $pdo->prepare('SELECT * FROM gebruikers WHERE leeftijd > ? AND actief = ?');
$stmt->execute([25, 1]);
$resultaten = $stmt->fetchAll(PDO::FETCH_ASSOC);
Benoemde placeholders (:naam) zijn leesbaarder dan vraagtekens, zeker als je meerdere parameters hebt.
Doctrine ORM — SQL zonder SQL
Een ORM (Object-Relational Mapper) koppelt databasetabellen aan PHP-klassen. Je werkt met objecten in plaats van SQL — Doctrine is de standaard ORM in Symfony.
$stmt = $pdo->prepare(
'SELECT * FROM gebruikers WHERE id = :id'
);
$stmt->execute([':id' => 1]);
$row = $stmt->fetch();
echo $row['naam'];
$gebruiker = $entityManager
->find(Gebruiker::class, 1);
echo $gebruiker->getNaam();
#[Entity]
#[Table(name: 'gebruikers')]
class Gebruiker {
#[Id, GeneratedValue, Column]
private int $id;
#[Column(length: 255)]
private string $naam;
#[Column(unique: true)]
private string $email;
public function getNaam(): string { return $this->naam; }
}
// Doctrine heeft zijn eigen querytaal (lijkt op SQL)
$gebruikers = $entityManager->createQuery(
'SELECT g FROM App\Entity\Gebruiker g WHERE g.leeftijd > :leeftijd'
)->setParameter('leeftijd', 25)->getResult();
Database design — best practices
orders: klant_naam, klant_email,
product_naam, product_prijsorders: gebruiker_id, product_id
-- via JOIN ophalenaangemaakt_op TEXT DEFAULT (datetime('now')),
gewijzigd_op TEXT DEFAULT (datetime('now'))
col1, data, x, valgebruiker_naam, bestel_datum, totaal_prijsCREATE INDEX idx_orders_gebruiker_id ON orders (gebruiker_id);
CREATE INDEX idx_orders_product_id ON orders (product_id);
Sla prijs en aantal op, niet totaal_prijs. Bereken het met SQL: prijs * aantal.
Eindoefening — alles samenbrengen
Gebruik alles wat je geleerd hebt in één grote query. De database is vooraf gevuld.
- Toon per gebruiker: naam, aantal bestellingen, totale omzet — gesorteerd op omzet
- Welke producten zijn nog nooit besteld? (subquery of LEFT JOIN + IS NULL)
- Toon de meest actieve dag (datum met de meeste bestellingen)
- Maak een nieuw product en voeg een bijbehorende bestelling in voor gebruiker 1
🧠 Kennischeck
Les 10 afgerond!
Je weet hoe je SQL koppelt aan PHP, hoe je SQL injection voorkomt en hoe Doctrine ORM werkt.