Menu

Обработка форм в PHP: приём, проверка и повторный вывод

PHP-форма отправляется сама на себя через <form method="post">, а скрипт читает поля из $_POST['name'], когда $_SERVER['REQUEST_METHOD'] равен 'POST'. Проверка данных, сообщения об ошибках рядом с полями, сохранение введённых значений, чекбоксы и списки select и экранирование вывода.

На этой странице есть исполняемые редакторы: меняйте, запускайте и сразу видите результат.

PHP-форма отправляется обратно в скрипт, который её вывел: пишите <form method="post"> с именованными полями, а когда $_SERVER['REQUEST_METHOD'] равен 'POST', читайте каждое поле как $_POST['name']. Всё, что выводите обратно, экранируйте через htmlspecialchars().

Запустите этот блок, введите в форму имя и нажмите Send: тот же скрипт выполнится снова, на этот раз с заполненным $_POST.

Здесь происходят три вещи, которые нужны каждому обработчику формы:

  • Проверка метода. При первом визите запрос это GET, и обрабатывать нечего. ?? 'GET' важен только тогда, когда файл запускается из командной строки, где REQUEST_METHOD не задан; на веб-сервере он задан всегда.
  • ?? '' для каждого поля. Поля может не быть (переименованный input, запрос, отправленный скриптом), а чтение отсутствующего ключа печатает предупреждение.
  • Экранирование при выводе. Имя возвращается на страницу дважды, и htmlspecialchars() гарантирует, что значение вроде <b>Ada</b> показывается как текст, а не становится разметкой. Подробности на странице про htmlspecialchars.

Проверка формы и ошибки рядом с полями

Собирайте ошибки в массив с ключами по именам полей. Если массив пуст, форма корректна; иначе выведите форму снова с каждым сообщением под своим полем, сохранив введённые посетителем данные. Отправьте её пустой, потом с неверным email, потом с корректными значениями.

novalidate отключает собственные проверки браузера, чтобы вы видели, как работают проверки PHP. В реальной форме оставляйте обе: проверка в браузере это удобство для посетителя, а проверка в PHP это та, которая действительно имеет значение, потому что любой может отправить запрос без вашей формы. filter_var() проверяет email и диапазон целого числа одним вызовом каждый; другие фильтры описаны на странице про filter_var.

Чекбоксы, радиокнопки и select

Текстовое поле отправляет значение всегда, даже пустое. Остальные элементы нет:

  • Неотмеченный чекбокс не отправляет вообще ничего, поэтому его ключа в $_POST нет.
  • Чекбоксы с именем topics[] приходят массивом: $_POST['topics'] это ['php', 'sql'].
  • Группа радиокнопок или <select> отправляет value выбранного варианта.

Чтобы сохранить выбор после отправки, выводите checked или selected у совпавшего варианта. Отметьте несколько чекбоксов, выберите уровень и нажмите Save.

Две проверки после чтения ввода важнее, чем кажется. array_intersect() отбрасывает любую тему, которой не было в вашем списке, а проверка is_string() вместе с array_key_exists() возвращает уровню значение по умолчанию, если кто-то отправит значение, которого вы не предлагали (или массив, из-за которого $levels[$level] выбросил бы ошибку). Select ограничивает то, что может отправить браузер, но не то, что может содержать запрос.

Многошаговые формы со скрытыми полями

Скрытое поле переносит значение с одного шага на следующий, не показывая его. Здесь шаг 1 спрашивает имя, шаг 2 спрашивает язык и передаёт имя дальше в скрытом поле, а шаг 3 показывает оба. Пройдите все три шага.

Скрытые поля видны в исходном коде страницы и редактируются, как любое другое поле, поэтому они годятся, чтобы переносить между шагами собственные ответы посетителя, но никогда не для цены, ID пользователя или роли. Храните такое на сервере, в сессии; смотрите сессии и cookie.

Post/Redirect/Get: не отправлять форму повторно при обновлении

Если после успешного POST скрипт сразу выводит страницу "спасибо", то при нажатии обновления браузер отправит POST снова, и заказ или комментарий сохранится дважды. Решение: сохранить, сделать редирект и показать результат по GET:

<?php
session_start();

if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    $errors = validate($_POST);
    if (!$errors) {
        save_comment($_POST);
        $_SESSION['flash'] = 'Comment posted.';
        header('Location: /comments.php', true, 303);
        exit;
    }
    // On errors, fall through and print the form with the messages.
}

$flash = $_SESSION['flash'] ?? null;
unset($_SESSION['flash']);

303 See Other говорит браузеру выполнить редирект через GET. exit после header() важен: без него остаток скрипта продолжает выполняться. Кроме того, редирект должен произойти до любого вывода, иначе PHP откажет с ошибкой "headers already sent".

В реальных формах добавляют ещё CSRF-токен: случайное значение, которое хранится в сессии и в скрытом поле и при отправке сравнивается через hash_equals(), чтобы другой сайт не мог отправить данные в вашу форму от имени вошедшего посетителя.

Проверка валидации без браузера

Поместите правила в функцию, которая принимает массив и возвращает ошибки. Страница вызывает её с $_POST, а вы можете вызвать её с любым массивом, и тогда пограничные случаи легко проверить. Добавьте в список свои случаи.

Четвёртый случай обычно забывают. Запрос может отправить name[]=x, и тогда $_POST['name'] станет массивом, а передача массива в trim() выбрасывает TypeError и останавливает скрипт. Предварительная проверка is_string() превращает враждебный ввод в обычную ошибку валидации. Приведение (string) в предыдущих блоках тоже избегает TypeError, но превращает массив в слово Array и печатает предупреждение, поэтому is_string() более чистая проверка.

Частая ошибка: чтение $_POST до отправки формы

Самый частый первый скрипт формы читает поля в начале файла:

<?php
$name = $_POST['name'];
echo "Hello, $name";

При первом визите ничего не отправлено, поэтому PHP печатает:

Warning: Undefined array key "name" in /var/www/form.php on line 2

Решение это шаблон из первого блока: обрабатывайте форму, только когда запрос это POST, и читайте каждое поле со значением по умолчанию, $_POST['name'] ?? ''. Если предупреждение появляется после отправки, сравните атрибут name в HTML с ключом в PHP: name="Name" и $_POST['name'] это разные ключи.

Часто задаваемые вопросы

Как отправить PHP-форму на ту же страницу?

Пишите <form method="post"> без action (или с action=""). Браузер отправит данные на текущий URL, поэтому один и тот же скрипт показывает форму и обрабатывает её: проверьте $_SERVER['REQUEST_METHOD'] === 'POST' и читайте поля из $_POST.

Как сохранить значения формы после отправки в PHP?

Выводите отправленное значение обратно в поле, экранировав его: <input name="email" value="<?= htmlspecialchars($_POST['email'] ?? '') ?>">. Для чекбокса выводите checked, а для пункта select selected, когда отправленное значение совпадает.

Как получить значения нескольких чекбоксов в PHP?

Дайте чекбоксам одинаковое имя со скобками в конце, name="topics[]". Тогда PHP сложит отмеченные значения в массив $_POST['topics']. Неотмеченные чекбоксы ничего не отправляют, поэтому используйте $_POST['topics'] ?? [] и проверяйте каждое значение по списку, который вы предлагали.

Почему обновление страницы повторно отправляет PHP-форму?

Последний запрос был POST, поэтому при обновлении браузер повторяет его. После успешной отправки сделайте редирект через header('Location: /thanks.php'); exit; (шаблон Post/Redirect/Get). Тогда обновление повторит безобидный GET.

Безопасно ли использовать $_POST в PHP?

Нет, каждое значение в $_POST приходит от посетителя и может быть чем угодно, в том числе HTML, SQL или массивом там, где вы ждали строку. Проверяйте его на входе (filter_var, длина и список допустимых значений) и экранируйте на выходе (htmlspecialchars для HTML, подготовленные запросы для SQL).

Иллюстрация языков программирования Coddy

Учитесь программировать с Coddy

НАЧАТЬ