← Back to the course

A TEACHING EXAMPLE · PHP

From a form to a contact list.

Validate a name and email, save a record, and display it safely. Explore the interaction, then inspect the PHP implementation.

Try the interaction

Use made-up details. This demonstration keeps records only in this page’s memory and sends nothing to a server. Refresh to clear them.

No example contacts yet.

    BUILD IT YOURSELF

    Understand each step.

    1. Show a labeled form and protect submissions with a CSRF token.
    2. Validate both values before creating a record.
    3. Insert values using a prepared PDO statement.
    4. Escape database values when rendering the contact list.
    Download example source

    Save as contact-demo.php. Use a local MySQL test database and the setup comments in the source. This is a local teaching example, not a public contact management service.

    Read the PHP source
    <?php
    /* LOCAL TEACHING EXAMPLE ONLY. Save as contact-demo.php in a separate folder.
     * Create a local MySQL test database and a restricted test user, then create:
     * CREATE TABLE contacts (id INT AUTO_INCREMENT PRIMARY KEY,
     *   name VARCHAR(80) NOT NULL, email VARCHAR(150) NOT NULL) ENGINE=InnoDB;
     * Set DEMO_DB_DSN (mysql:host=127.0.0.1;dbname=YOUR_TEST_DB;charset=utf8mb4),
     * DEMO_DB_USER and DEMO_DB_PASS in your local shell. No credentials are supplied.
     * Run php -S 127.0.0.1:8000 and open /contact-demo.php.
     * This example has no multi-user ownership model. Do not publish it as a service.
     */
    if (!in_array($_SERVER['REMOTE_ADDR'] ?? '', ['127.0.0.1', '::1'], true)) {
        http_response_code(403); exit('Local teaching example only.');
    }
    session_start(['cookie_httponly'=>true, 'cookie_samesite'=>'Strict', 'use_strict_mode'=>true]);
    header('X-Content-Type-Options: nosniff');
    header('Cache-Control: no-store');
    $_SESSION['csrf'] ??= bin2hex(random_bytes(32));
    function e(string $value): string { return htmlspecialchars($value, ENT_QUOTES, 'UTF-8'); }
    $error = ''; $rows = [];
    try {
        $pdo = new PDO((string)getenv('DEMO_DB_DSN'), (string)getenv('DEMO_DB_USER'), (string)getenv('DEMO_DB_PASS'),
            [PDO::ATTR_ERRMODE=>PDO::ERRMODE_EXCEPTION, PDO::ATTR_EMULATE_PREPARES=>false]);
        if ($_SERVER['REQUEST_METHOD'] === 'POST') {
            $token = is_string($_POST['csrf'] ?? null) ? $_POST['csrf'] : '';
            if (!hash_equals($_SESSION['csrf'], $token)) { http_response_code(403); exit('Reload the form.'); }
            $name = is_string($_POST['name'] ?? null) ? trim($_POST['name']) : '';
            $email = is_string($_POST['email'] ?? null) ? trim($_POST['email']) : '';
            if ($name === '' || strlen($name) > 80 || strlen($email) > 150 || !filter_var($email, FILTER_VALIDATE_EMAIL)) {
                $error = 'Enter a name up to 80 bytes and a valid email up to 150 bytes.';
            } else {
                $statement = $pdo->prepare('INSERT INTO contacts (name, email) VALUES (?, ?)');
                $statement->execute([$name, $email]);
                header('Location: contact-demo.php', true, 303); exit;
            }
        }
        $rows = $pdo->query('SELECT name, email FROM contacts ORDER BY id DESC LIMIT 20')->fetchAll(PDO::FETCH_ASSOC);
    } catch (PDOException $exception) {
        error_log('Local contact demo database operation failed.');
        $error = 'Check the local database setup. The contact could not be loaded or saved.';
    }
    ?>
    <!doctype html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width"><title>Local contact exercise</title></head>
    <body><main><h1>Local contact exercise</h1><p>Use made-up details in this local test database.</p>
    <?php if ($error): ?><p role="alert"><?= e($error) ?></p><?php endif; ?>
    <form method="post"><input type="hidden" name="csrf" value="<?= e($_SESSION['csrf']) ?>">
    <p><label for="name">Name</label> <input id="name" name="name" maxlength="80" required></p>
    <p><label for="email">Email</label> <input id="email" name="email" type="email" maxlength="150" required></p>
    <button type="submit">Save example contact</button></form>
    <h2>Latest contacts</h2><ul><?php foreach ($rows as $row): ?><li><?= e($row['name']) ?> — <?= e($row['email']) ?></li><?php endforeach; ?></ul>
    </main></body></html>