Перейти к основному содержимому

От HTML-формы до записи в базу данных на PHP

Разработчику Архитектору

Один сквозной сценарий связывает темы форм, PDO и исключений. Без фреймворка — чтобы было видно каждый шаг.


Схема потока


Таблица и подключение

CREATE TABLE subscribers (
id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(100) NOT NULL,
email VARCHAR(255) NOT NULL,
created_at DATETIME NOT NULL,
UNIQUE KEY uq_email (email)
);

Файл bootstrap.php — общее подключение PDO (подключается из скриптов):

<?php
declare(strict_types=1);

$pdo = new PDO(
'mysql:host=127.0.0.1;dbname=app;charset=utf8mb4',
getenv('DB_USER') ?: 'app_user',
getenv('DB_PASS') ?: '',
[
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
],
);

Шаг 1 — форма (GET)

public/register.php:

<?php
declare(strict_types=1);
?>
<!DOCTYPE html>
<html lang="ru">
<head><meta charset="utf-8"><title>Подписка</title></head>
<body>
<h1>Подписка на рассылку</h1>
<?php if (!empty($_GET['error'])): ?>
<p role="alert">Проверьте поля и попробуйте снова.</p>
<?php endif; ?>
<form action="register_submit.php" method="post">
<label>Имя <input name="name" required maxlength="100"></label>
<label>Email <input name="email" type="email" required maxlength="255"></label>
<button type="submit">Подписаться</button>
</form>
</body>
</html>

method="post" — данные в теле запроса, не в URL. Пароли и персональные данные не передают через GET.


Шаг 2 — приём и валидация (POST)

public/register_submit.php:

<?php
declare(strict_types=1);

require dirname(__DIR__) . '/bootstrap.php';

$name = trim((string) ($_POST['name'] ?? ''));
$email = trim((string) ($_POST['email'] ?? ''));

$errors = [];

if ($name === '') {
$errors['name'] = 'Укажите имя';
} elseif (mb_strlen($name) > 100) {
$errors['name'] = 'Имя слишком длинное';
}

if ($email === '') {
$errors['email'] = 'Укажите email';
} elseif (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
$errors['email'] = 'Некорректный email';
}

if ($errors !== []) {
// Упрощение: редирект с флагом; в реальном проекте — сессия с полями и ошибками
header('Location: register.php?error=1');
exit;
}

Фильтрация на сервере обязательна: клиентская проверка в браузере обходится за секунду.


Шаг 3 — запись через PDO

try {
$stmt = $pdo->prepare(
'INSERT INTO subscribers (name, email, created_at) VALUES (:name, :email, :created_at)'
);
$stmt->execute([
'name' => $name,
'email' => $email,
'created_at' => (new DateTimeImmutable('now'))->format('Y-m-d H:i:s'),
]);
} catch (PDOException $e) {
// Дубликат email (код 23000 — integrity constraint)
if ((int) ($e->errorInfo[1] ?? 0) === 1062) {
header('Location: register.php?error=duplicate');
exit;
}
error_log($e->getMessage());
http_response_code(500);
echo 'Ошибка сервера';
exit;
}

header('Location: thanks.php', true, 302);
exit;

Паттерн Post/Redirect/Get: после успешного POST браузер получает редирект на thanks.php. Обновление страницы не повторяет INSERT.


Шаг 4 — страница благодарности

public/thanks.php — статичный HTML «Спасибо за подписку». Без повторной обработки POST.


Безопасность в этом сценарии

УгрозаМера
SQL-инъекцияТолько prepare + именованные параметры
XSS при выводе ошибокhtmlspecialchars($msg, ENT_QUOTES, 'UTF-8') для любого текста из POST
Повторная отправка формыРедирект после успешного POST
CSRFТокен в сессии и скрытое поле формы (см. сессии)
Перебор emailRate limit на уровне веб-сервера или кэша

Развитие сценария

  1. Вынести валидацию в класс App\Validation\SubscribeValidatorпространства имён.
  2. Хранить ошибки в $_SESSION и показывать их рядом с полями — сессии.
  3. Статус подписки задать через enum.
  4. Перейти на маршрутизацию фреймворка — Laravel или Symfony.

Что изучить дальше


См. также

Другие статьи этого же раздела в боковом меню (как на странице «О разделе»).