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.
- Show a labeled form and protect submissions with a CSRF token.
- Validate both values before creating a record.
- Insert values using a prepared PDO statement.
- Escape database values when rendering the contact list.
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>