<

Stap 11 — Contactformulier (PHP) → opslaan in MariaDB

We maken een contactformulier met Bootstrap. De invoer wordt server-side gevalideerd en veilig bewaard in MariaDB via PDO prepared statements.

11.1 Voorbereiding: draai dit via PHP (niet via Live Server)

  • Zet je project in de webroot (bv. htdocs/project/ bij XAMPP/MAMP/WAMP) of start lokaal: php -S localhost:8000 in de project/ map.
  • Open de site via http://localhost/… (niet via 127.0.0.1:5500).

11.2 Database & tabel aanmaken (MariaDB / MySQL)

Voer dit SQL-script éénmalig uit (via phpMyAdmin of CLI):

CREATE DATABASE IF NOT EXISTS projectdb
  DEFAULT CHARACTER SET utf8mb4
  COLLATE utf8mb4_unicode_ci;

USE projectdb;

CREATE TABLE IF NOT EXISTS contact_messages (
  id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
  name VARCHAR(100) NOT NULL,
  email VARCHAR(255) NOT NULL,
  message TEXT NOT NULL,
  created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

11.3 DB-config (PDO)

Maak config.php in je project (buiten assets/):

<?php
// config.php — eenvoudige PDO-connector
$DB_HOST = 'localhost';
$DB_NAME = 'projectdb';
$DB_USER = 'root';     // XAMPP/MAMP default
$DB_PASS = '';         // vaak leeg op XAMPP; pas aan indien nodig

$dsn = "mysql:host=$DB_HOST;dbname=$DB_NAME;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, $DB_USER, $DB_PASS, $options);
} catch (PDOException $e) {
  exit('DB-verbinding mislukt: ' . htmlspecialchars($e->getMessage()));
}
Tip: zet config.php later in includes/ en sluit ‘m uit in Git (.gitignore).

11.4 Formulier + verwerking (één bestand)

Maak contact.php. Bovenaan staat PHP die POST verwerkt; daaronder de HTML met Bootstrap.

<?php
require __DIR__ . '/config.php';

$errors = [];
$success = false;

// Bij POST: valideren en opslaan
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
  // Trim input
  $name = trim($_POST['name'] ?? '');
  $email = trim($_POST['email'] ?? '');
  $message = trim($_POST['message'] ?? '');

  // Validatie
  if ($name === '' || mb_strlen($name) < 2) {
    $errors['name'] = 'Naam is verplicht (min. 2 tekens).';
  }
  if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
    $errors['email'] = 'Geef een geldig e-mailadres op.';
  }
  if ($message === '' || mb_strlen($message) < 10) {
    $errors['message'] = 'Schrijf een korte boodschap (min. 10 tekens).';
  }

  // Opslaan indien geldig
  if (!$errors) {
    $stmt = $pdo->prepare(
      'INSERT INTO contact_messages (name, email, message) VALUES (?, ?, ?)'
    );
    $stmt->execute([$name, $email, $message]);
    $success = true;

    // Form velden leegmaken na succes
    $name = $email = $message = '';
  }
}

// Helper voor repopuleren
function e($s){ return htmlspecialchars($s ?? '', ENT_QUOTES, 'UTF-8'); }
?>
<!doctype html>
<html lang="nl">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>Contact</title>
  <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">
  <link rel="stylesheet" href="assets/css/custom.css">
</head>
<body>

  <nav class="navbar navbar-expand-lg bg-body-tertiary fixed-top" id="site-navbar">...</nav>

  <main id="main-content" class="container" style="margin-top:5rem">

    <h1 class="pt-3">Contact</h1>

    <?php if ($success): ?>
      <div class="alert alert-success">Bedankt! Je bericht werd verzonden.</div>
    <?php endif; ?>

    <form method="post" novalidate class="row g-3">
      <div class="col-md-6">
        <label for="name" class="form-label">Naam</label>
        <input type="text" class="form-control <?= isset($errors['name']) ? 'is-invalid' : '' ?>" id="name" name="name" value="<?= e($name ?? '') ?>" required>
        <div class="invalid-feedback"><?= $errors['name'] ?? '' ?></div>
      </div>

      <div class="col-md-6">
        <label for="email" class="form-label">E-mail</label>
        <input type="email" class="form-control <?= isset($errors['email']) ? 'is-invalid' : '' ?>" id="email" name="email" value="<?= e($email ?? '') ?>" required>
        <div class="invalid-feedback"><?= $errors['email'] ?? '' ?></div>
      </div>

      <div class="col-12">
        <label for="message" class="form-label">Boodschap</label>
        <textarea class="form-control <?= isset($errors['message']) ? 'is-invalid' : '' ?>" id="message" name="message" rows="5" required><?= e($message ?? '') ?></textarea>
        <div class="invalid-feedback"><?= $errors['message'] ?? '' ?></div>
      </div>

      <div class="col-12">
        <button type="submit" class="btn btn-primary">Verzenden</button>
      </div>
    </form>

    <hr class="my-4">

    <h2 class="h5">Ingekomen berichten (laatste 5)</h2>
    <?php
      $rows = $pdo->query('SELECT name, email, message, created_at FROM contact_messages ORDER BY id DESC LIMIT 5')->fetchAll();
      if ($rows):
    ?>
      <div class="list-group mb-5">
        <?php foreach($rows as $r): ?>
          <div class="list-group-item">
            <div class="d-flex justify-content-between">
              <strong><?= e($r['name']) ?></strong>
              <small class="text-muted"><?= e($r['created_at']) ?></small>
            </div>
            <div class="small text-muted"><?= e($r['email']) ?></div>
            <p class="mb-0"><?= nl2br(e($r['message'])) ?></p>
          </div>
        <?php endforeach; ?>
      </div>
    <?php else: ?>
      <p class="text-muted">Nog geen berichten.</p>
    <?php endif; ?>

  </main>

  <footer class="site-footer bg-dark text-light mt-5" id="site-footer">...</footer>

  <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script>
</body>
</html>

11.5 Snel testen

  • Open /contact.php via http://localhost/….
  • Verzend met lege velden → je ziet nette foutmeldingen.
  • Verzend geldig → “Bedankt!” + verschijnt in de lijst “laatste 5”.

Checklist

  • Database projectdb en tabel contact_messages bestaan.
  • config.php werkt (PDO met UTF-8 en exceptions).
  • Formulier gebruikt prepared statements; invoer wordt gevalideerd.
Verderop (optioneel): CSRF-token, server-side rate limiting, e-mailnotificatie via SMTP, en een klein admin-overzicht.