SQL-trackLes 10 van 10

🚀 SQL in de praktijk

Van query naar applicatie — PHP PDO, prepared statements, Doctrine ORM en wat nu?

⏱ 30 min6 stappenLaatste les van de SQL-track
Stap 1 / 6

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.

Verbinding maken
<?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');
Een eenvoudige query uitvoeren
// Alle gebruikers ophalen
$stmt = $pdo->query('SELECT * FROM gebruikers');
$gebruikers = $stmt->fetchAll(PDO::FETCH_ASSOC);

foreach ($gebruikers as $gebruiker) {
    echo $gebruiker['naam'] . ' — ' . $gebruiker['email'];
}
🚨 Doe dit NOOIT — SQL Injection!
// 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
Stap 2 / 6

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.

✅ Veilig — met prepared statements
// 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,
]);
Vraagtekens als placeholder (alternatief)
// Positie-gebaseerde placeholders
$stmt = $pdo->prepare('SELECT * FROM gebruikers WHERE leeftijd > ? AND actief = ?');
$stmt->execute([25, 1]);
$resultaten = $stmt->fetchAll(PDO::FETCH_ASSOC);
Gebruik altijd :naam placeholders

Benoemde placeholders (:naam) zijn leesbaarder dan vraagtekens, zeker als je meerdere parameters hebt.

Stap 3 / 6

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.

Met PDO + SQL
$stmt = $pdo->prepare(
    'SELECT * FROM gebruikers WHERE id = :id'
);
$stmt->execute([':id' => 1]);
$row = $stmt->fetch();
echo $row['naam'];
Met Doctrine ORM
$gebruiker = $entityManager
    ->find(Gebruiker::class, 1);
echo $gebruiker->getNaam();
Entity — PHP-klasse als tabel
#[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; }
}
DQL — Doctrine Query Language
// 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();
Stap 4 / 6

Database design — best practices

1. Normalisatie — vermijd herhaling
❌ Denormalized
orders: klant_naam, klant_email,
        product_naam, product_prijs
✅ Normalized
orders: gebruiker_id, product_id
-- via JOIN ophalen
2. Altijd timestamps — aangemaakt_op & gewijzigd_op
aangemaakt_op  TEXT DEFAULT (datetime('now')),
gewijzigd_op   TEXT DEFAULT (datetime('now'))
3. Gebruik betekenisvolle kolomnamen
❌ Slecht
col1, data, x, val
✅ Goed
gebruiker_naam, bestel_datum, totaal_prijs
4. Foreign key indexen — altijd!
CREATE INDEX idx_orders_gebruiker_id ON orders (gebruiker_id);
CREATE INDEX idx_orders_product_id   ON orders (product_id);
5. Sla geen berekende waarden op

Sla prijs en aantal op, niet totaal_prijs. Bereken het met SQL: prijs * aantal.

Stap 5 / 6

Eindoefening — alles samenbrengen

Gebruik alles wat je geleerd hebt in één grote query. De database is vooraf gevuld.

🎯 Eindopdrachten
  1. Toon per gebruiker: naam, aantal bestellingen, totale omzet — gesorteerd op omzet
  2. Welke producten zijn nog nooit besteld? (subquery of LEFT JOIN + IS NULL)
  3. Toon de meest actieve dag (datum met de meeste bestellingen)
  4. Maak een nieuw product en voeg een bijbehorende bestelling in voor gebruiker 1
Resultaat verschijnt hier...

🧠 Kennischeck

🗄️

Les 10 afgerond!

Je weet hoe je SQL koppelt aan PHP, hoe je SQL injection voorkomt en hoe Doctrine ORM werkt.