Формы PHP: от создания до защиты данных

Раздел: Веб-разработка на PHP -> Основы форм

Основы работы с формами в PHP

Как реализовать безопасную и эффективную обработку формы в одном файле?

Самый удобный способ - разместить HTML-форму и PHP-обработчик в одном файле (self-submitting form). Это упрощает поддержку и позволяет сохранять введённые данные при ошибках валидации. Для защиты от CSRF-атак добавляют токен, хранящийся в сессии.


<?php
session_start();
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    if (!hash_equals($_SESSION['token'], $_POST['token'])) {
        die('Ошибка валидации токена');
    }
    $name = htmlspecialchars($_POST['name'] ?? '', ENT_QUOTES, 'UTF-8');
    $email = filter_var($_POST['email'] ?? '', FILTER_VALIDATE_EMAIL);
    if ($email === false) {
        $error = 'Некорректный email';
    } else {
        // Обработка данных (например, запись в БД)
        $success = 'Данные приняты';
    }
}
$_SESSION['token'] = bin2hex(random_bytes(32));
?>
<!DOCTYPE html>
<html>
<body>
<?php if (!empty($error)): ?>
  <p class="fw-bold"><?= $error ?></p>
<?php endif; ?>
<?php if (!empty($success)): ?>
  <p class="fw-bold"><?= $success ?></p>
<?php endif; ?>
<form method="post">
  <input type="hidden" name="token" value="<?= $_SESSION['token'] ?>">
  <input type="text" name="name" placeholder="Имя" value="<?= htmlspecialchars($_POST['name'] ?? '', ENT_QUOTES) ?>">
  <input type="email" name="email" placeholder="Email" value="<?= htmlspecialchars($_POST['email'] ?? '', ENT_QUOTES) ?>">
  <button type="submit">Отправить</button>
</form>
</body>
</html>
  

Пояснение: токен генерируется каждый запрос, вставляется в скрытое поле и проверяется при POST. Для вывода используется htmlspecialchars, чтобы избежать XSS. Валидация email - через filter_var. Значения полей сохраняются из предыдущего POST, чтобы пользователь не терял ввод при ошибке.

Типичные ошибки и их решения:

  • Забывают проверять $_SERVER['REQUEST_METHOD'] - обработчик выполняется и при GET-запросе, выдавая ошибки. Решение: оборачивать логику в условие.
  • Не экранируют вывод - приводит к XSS. Решение: всегда использовать htmlspecialchars с корректной кодировкой.
  • Токен CSRF не обновляется - возможна атака. Решение: генерировать новый токен после каждой отправки (или использовать одноразовые токены).
  • Не проверяют hash_equals, а используют == - уязвимость к timing attack. Решение: использовать hash_equals.

Как обработать форму с раздельными файлами формы и обработчика?

Традиционный подход, когда HTML-форма в одном файле (например, form.html), а обработка в другом (process.php). Подходит для простых сценариев, где не требуется сохранять состояние.


<!-- form.html -->
<form action="process.php" method="post">
  <input type="text" name="login">
  <input type="password" name="pass">
  <button>Войти</button>
</form>

<!-- process.php -->
<?php
$login = $_POST['login'] ?? '';
$pass = $_POST['pass'] ?? '';
// валидация и логика
header('Location: dashboard.php'); // редирект после успеха
?>
  

Проблема: при редиректе теряются сообщения об ошибках, и пользователь не видит, что пошло не так. Решение: использовать сессию для flash-сообщений или вернуться к самоотправляемой форме.

Ошибки: отсутствие проверки существования ключей в $_POST (вызовет warning). Решение: использовать оператор ??.

Как использовать метод GET для форм поиска?

Для поисковых форм, результаты которых можно закладкать, используют GET. Параметры видны в URL.


<form method="get" action="/search">
  <input type="text" name="q">
  <button>Искать</button>
</form>
<!-- PHP -->
<?php
$query = htmlspecialchars($_GET['q'] ?? '');
echo "Результаты для: $query";
?>
  

Важно: не использовать GET для передачи чувствительных данных (пароли, личные сведения), так как они попадают в историю браузера и логи сервера.

Как обработать массив полей (например, с использованием name[])?

Для полей, которые могут повторяться (например, список телефонов), используют синтаксис name[]. PHP преобразует их в массив.


<form method="post">
  <input type="text" name="phones[]">
  <input type="text" name="phones[]">
  <button>Отправить</button>
</form>
<?php
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    $phones = $_POST['phones'] ?? [];
    foreach ($phones as $phone) {
        $clean = preg_replace('/[^0-9+\-]/', '', $phone);
        // сохранение
    }
}
?>
  

Типичная ошибка: не проверять, что $_POST['phones'] действительно массив. Если отправлено одно поле, может быть строка. Решение: явно приводить к массиву через (array) или [].

Как защитить форму от XSS при выводе данных из базы?

Даже если данные введены корректно, при выводе их из БД нужно экранировать. Для контекста HTML - htmlspecialchars, для URL - urlencode, для JavaScript - json_encode.


<?php
$user_comment = "<script>alert('XSS')</script>";
echo htmlspecialchars($user_comment, ENT_QUOTES, 'UTF-8');
// результат: &lt;script&gt;alert('XSS')&lt;/script&gt;
?>
  

Ошибка: использование устаревшей htmlentities без указания кодировки может привести к двойному экранированию. Решение: всегда указывать кодировку UTF-8.

Расширенные примеры работы с формами в PHP

Пример: загрузка файла с проверкой типа и размера

Для загрузки файлов форма должна содержать атрибут enctype="multipart/form-data". В PHP файл доступен в $_FILES. Проверим MIME-тип (реальный, не из заголовка) и размер.

Пример

<form method="post" enctype="multipart/form-data">
  <input type="file" name="avatar" accept="image/*">
  <button>Загрузить</button>
</form>
<?php
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    $file = $_FILES['avatar'] ?? null;
    if ($file && $file['error'] === UPLOAD_ERR_OK) {
        $finfo = finfo_open(FILEINFO_MIME_TYPE);
        $mime = finfo_file($finfo, $file['tmp_name']);
        finfo_close($finfo);
        $allowed = ['image/jpeg', 'image/png', 'image/gif'];
        if (in_array($mime, $allowed) && $file['size'] < 2*1024*1024) {
            move_uploaded_file($file['tmp_name'], 'uploads/' . bin2hex(random_bytes(16)) . '.jpg');
            echo 'Файл загружен';
        } else {
            echo 'Неверный тип или размер';
        }
    } else {
        echo 'Ошибка загрузки';
    }
}
?>
  

Результат: при успешной загрузке файл перемещается в папку uploads с безопасным именем (случайная строка). При ошибке выводится сообщение.

Файл загружен
  

Типичная ошибка: доверять $_FILES['file']['type'] - это значение из заголовка, которое можно подделать. Решение: определять MIME-тип через finfo.

Пример: форма с AJAX (fetch) и JSON-ответом

Современные формы отправляют данные асинхронно, не перезагружая страницу. PHP возвращает JSON, который обрабатывается на клиенте.

Пример

<script>
document.getElementById('ajaxForm').addEventListener('submit', async (e) => {
    e.preventDefault();
    const formData = new FormData(e.target);
    const response = await fetch('handler.php', { method: 'POST', body: formData });
    const result = await response.json();
    if (result.success) {
        document.getElementById('msg').textContent = 'Ok';
    } else {
        document.getElementById('msg').textContent = result.error;
    }
});
</script>
<form id="ajaxForm">
  <input type="text" name="name">
  <button>Отправить AJAX</button>
</form>
<div id="msg"></div>

<!-- handler.php -->
<?php
header('Content-Type: application/json');
$name = htmlspecialchars($_POST['name'] ?? '');
if (mb_strlen($name) < 2) {
    echo json_encode(['success' => false, 'error' => 'Слишком короткое имя']);
    exit;
}
// логика
 echo json_encode(['success' => true]);
?>
  

Результат: без перезагрузки страницы отображается сообщение об успехе или ошибке. Важно: не забыть установить заголовок Content-Type.

Пример: форма с сохранением состояния в сессии (многошаговая)

При длинных формах данные разбивают на шаги, храня промежуточные значения в сессии.

Пример

<?php
session_start();
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['step'])) {
    $step = (int)$_POST['step'];
    // сохраняем данные текущего шага
    foreach ($_POST as $key => $val) {
        if ($key !== 'step' && $key !== 'token') {
            $_SESSION['form'][$key] = htmlspecialchars($val);
        }
    }
    if ($step == 1) {
        // переход на шаг 2
        $next_step = 2;
    } else {
        // последний шаг - обработка
        // сохранение из $_SESSION['form'] в БД
        session_destroy();
        echo 'Форма завершена';
        exit;
    }
}
$next_step = $next_step ?? 1;
?>
<form method="post">
  <input type="hidden" name="step" value="<?= $next_step ?>">
  <?php if ($next_step == 1): ?>
    <input type="text" name="name" value="<?= $_SESSION['form']['name'] ?? '' ?>">
  <?php elseif ($next_step == 2): ?>
    <input type="email" name="email" value="<?= $_SESSION['form']['email'] ?? '' ?>">
  <?php endif; ?>
  <button>Далее</button>
</form>
  

Результат: пошаговое заполнение, при возврате данные сохраняются из сессии. Ошибка: не удалять сессию после завершения - данные могут остаться в памяти.

Пример: обработка формы с динамическими полями (JavaScript + PHP)

Пользователь может добавлять и удалять поля на лету. На сервер поля приходят как массив.

Пример

<div id="fields">
  <input type="text" name="items[]">
</div>
<button id="addField">+</button>
<script>
document.getElementById('addField').onclick = function() {
    var div = document.getElementById('fields');
    var input = document.createElement('input');
    input.type = 'text';
    input.name = 'items[]';
    div.appendChild(input);
};
</script>
<?php
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    $items = $_POST['items'] ?? [];
    foreach ($items as $item) {
        $clean = trim(strip_tags($item));
        // обработка
    }
}
?>
  

Проблема: если не удалять пустые поля, они могут быть обработаны. Решение: фильтровать массив через array_filter.

Пример: защита от повторной отправки формы (POST/Redirect/GET)

Чтобы данные не отправлялись при обновлении страницы после POST, используют редирект.

Пример

<?php
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    // обработка
    $_SESSION['flash'] = 'Данные сохранены';
    header('Location: ' . $_SERVER['PHP_SELF']);
    exit;
}
?>
<?php if (isset($_SESSION['flash'])): ?>
  <p class="fw-bold"><?= $_SESSION['flash'] ?></p>
  <?php unset($_SESSION['flash']); ?>
<?php endif; ?>
  

Результат: при обновлении страницы POST-запрос не повторяется, сообщение показывается один раз.

Формы в PHP - comments

En
форма php форме php (php)