Валидация форм в PHP
Валидация форм в PHP — проверка данных на сервере перед обработкой. Защита от XSS и SQL-инъекций с помощью htmlspecialchars и подготовленных запросов.
Валидация форм — это процесс проверки того, что данные, отправленные пользователем через HTML-форму, присутствуют, правильно отформатированы и безопасны для использования до того, как скрипт начнёт их обрабатывать. В PHP валидация выполняется на сервере после отправки формы, поэтому её нельзя обойти, отключив JavaScript или отправив запрос напрямую.
В этой главе рассматривается полный процесс валидации: почему важна серверная проверка, в чём разница между санитизацией и валидацией, какие PHP-функции для этого используются, а также полный безопасный пример, который можно адаптировать под свои нужды. Если вы только начинаете работать с вводом данных форм, сначала прочитайте Обработка форм в PHP.
Почему важна серверная валидация
Клиентская валидация (HTML5 required, type="email", JavaScript) улучшает пользовательский опыт, мгновенно выявляя ошибки — но это лишь вспомогательный инструмент. Целеустремлённый или злонамеренный пользователь может отключить JavaScript или отправить специально сформированный HTTP-запрос, минуя браузер полностью. Сервер — единственное место, где валидации можно доверять. Серверная валидация на PHP позволяет:
- Гарантировать, что обязательные поля действительно присутствуют и не пусты.
- Убедиться, что значения соответствуют ожидаемому формату (корректный email, число в допустимом диапазоне).
- Нейтрализовать опасные данные до того, как они попадут в базу данных или на HTML-страницу, предотвращая SQL-инъекции и межсайтовый скриптинг (XSS).
Считайте каждое значение в $_POST, $_GET и $_REQUEST ненадёжным до тех пор, пока не проверите его. Эти массивы поступают из суперглобальных переменных PHP.
Санитизация и валидация
Эти два понятия часто путают, но они выполняют разные функции:
- Санитизация очищает значение — она удаляет или экранирует нежелательные символы.
trim()убирает пробелы в начале и конце строки;htmlspecialchars()преобразует<,>,&и кавычки в HTML-сущности, чтобы они отображались как текст, а не выполнялись. - Валидация проверяет значение по правилу и сообщает, прошло оно или нет — при этом значение не изменяется.
filter_var($email, FILTER_VALIDATE_EMAIL)возвращает email, если он выглядит корректно, илиfalse, если нет.
Типичный порядок действий: сначала санитизация (обрезка пробелов), затем валидация, и наконец экранирование при выводе (когда значение вставляется обратно в HTML). Подробнее о системе фильтрации PHP читайте в PHP Filters и filter_var().
Шаги валидации
Процедура серверной валидации обычно состоит из одних и тех же четырёх шагов:
- Создать HTML-форму и указать в атрибуте
actionPHP-скрипт. - Определить отправку методом
POSTс помощью$_SERVER["REQUEST_METHOD"]. - Для каждого поля: считать значение, санитизировать его, затем проверить — собирая сообщения об ошибках.
- Если все поля прошли проверку — обработать данные; в противном случае — снова отобразить форму с сообщениями об ошибках и значениями, которые пользователь уже ввёл.
Полезные PHP-функции для валидации
| Функция | Назначение |
|---|---|
trim() | Удаляет пробельные символы в начале и конце строки. |
empty() | Проверяет, отсутствует ли значение или является ли оно пустой строкой. |
filter_var() | Валидирует или санитизирует значение с помощью встроенного фильтра (например, FILTER_VALIDATE_EMAIL, FILTER_VALIDATE_INT). |
is_numeric() | Возвращает true, если значение является числом или числовой строкой. |
htmlspecialchars() | Экранирует специальные HTML-символы для предотвращения XSS при выводе. |
preg_match() | Проверяет значение по заданному регулярному выражению. |
Минимальный пример
Прежде чем перейти к полному примеру с базой данных, рассмотрим простейшую валидацию, демонстрирующую основной паттерн — санитизация, затем валидация, затем сбор ошибок:
<?php
$email = trim($_POST["email"] ?? "");
$errors = [];
if ($email === "") {
$errors["email"] = "Please enter your email.";
} elseif (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
$errors["email"] = "Please enter a valid email address.";
}
if (empty($errors)) {
echo "Valid email: " . htmlspecialchars($email);
} else {
echo $errors["email"];
}
?>Если $_POST["email"] содержит " [email protected] ", окружающие пробелы обрезаются, значение проходит валидацию, и скрипт выводит Valid email: [email protected]. Строка вроде "not-an-email" приведёт к выводу Please enter a valid email address.
Полный пример: валидация и сохранение данных формы
Пример ниже проверяет имя, email, пол и комментарий, а затем вставляет строку с помощью подготовленного запроса — именно этот подход (с использованием mysqli) защищает от SQL-инъекций, поскольку пользовательские данные передаются отдельно от SQL-текста и никогда не интерпретируются как код.
Обновите учётные данные базы данных и убедитесь, что в базе данных существует таблица users со столбцами name, email, gender и comment, прежде чем запускать этот пример.
<?php
// Database connection setup
$link = mysqli_connect("localhost", "username", "password", "database");
if (!$link) {
die("Connection failed: " . mysqli_connect_error());
}
// Define variables and initialize with empty values
$name = $email = $gender = $comment = $website = "";
$name_err = $email_err = $gender_err = $comment_err = "";
// Processing form data when form is submitted
if($_SERVER["REQUEST_METHOD"] == "POST"){
// Validate name
if(empty(trim($_POST["name"] ?? ""))){
$name_err = "Please enter your name.";
} else{
$name = trim($_POST["name"]);
}
// Validate email
if(empty(trim($_POST["email"] ?? ""))){
$email_err = "Please enter your email.";
} elseif(!filter_var(trim($_POST["email"] ?? ""), FILTER_VALIDATE_EMAIL)) {
$email_err = "Please enter a valid email address.";
} else{
$email = trim($_POST["email"]);
}
// Validate gender
if(!isset($_POST["gender"]) || empty($_POST["gender"])){
$gender_err = "Please select your gender.";
} else{
$gender = $_POST["gender"];
}
// Validate comment
if(empty(trim($_POST["comment"] ?? ""))){
$comment_err = "Please enter your comment.";
} else{
$comment = trim($_POST["comment"]);
}
// Check input errors before inserting in database
if(empty($name_err) && empty($email_err) && empty($gender_err) && empty($comment_err)){
// Prepare an insert statement
$sql = "INSERT INTO users (name, email, gender, comment) VALUES (?, ?, ?, ?)";
if($stmt = mysqli_prepare($link, $sql)){
// Bind variables to the prepared statement as parameters
mysqli_stmt_bind_param($stmt, "ssss", $param_name, $param_email, $param_gender, $param_comment);
// Set parameters
$param_name = $name;
$param_email = $email;
$param_gender = $gender;
$param_comment = $comment;
// Attempt to execute the prepared statement
if(mysqli_stmt_execute($stmt)){
// Records created successfully. Redirect to landing page
header("location: index.php");
exit();
} else{
echo "Something went wrong. Please try again later.";
}
}
// Close statement
mysqli_stmt_close($stmt);
}
}
// Close connection
mysqli_close($link);
?>
<!DOCTYPE html>
<html>
<head><title>PHP Form Validation</title></head>
<body>
<form method="post" action="<?php echo htmlspecialchars($_SERVER["PHP_SELF"]); ?>">
Name: <input type="text" name="name" value="<?php echo htmlspecialchars($name); ?>">
<span class="error"><?php echo $name_err; ?></span><br><br>
Email: <input type="text" name="email" value="<?php echo htmlspecialchars($email); ?>">
<span class="error"><?php echo $email_err; ?></span><br><br>
Gender:
<input type="radio" name="gender" value="female"> Female
<input type="radio" name="gender" value="male"> Male
<span class="error"><?php echo $gender_err; ?></span><br><br>
Comment: <textarea name="comment"><?php echo htmlspecialchars($comment); ?></textarea>
<span class="error"><?php echo $comment_err; ?></span><br><br>
<input type="submit" value="Submit">
</form>
</body>
</html>Отображение ошибок и сохранение введённых данных
Обратите внимание на три детали в примере, которые обеспечивают хороший UX формы:
- Каждая переменная с ошибкой (
$name_err,$email_err, …) выводится в теге<span class="error">рядом с соответствующим полем, чтобы пользователь точно знал, что нужно исправить. - В атрибуте
actionформы используетсяhtmlspecialchars($_SERVER["PHP_SELF"]). Без экранированияPHP_SELFможет быть использован для внедрения скриптов через URL, поэтому экранирование здесь — мера безопасности, а не просто форматирование. - Каждое значение, повторно отображаемое в форме, передаётся через
htmlspecialchars(). Это одновременно предотвращает XSS и позволяет пользователю сохранить уже введённые данные, не перепечатывая всё заново из-за одного неверного поля.
Распространённые ошибки
empty()считает"0"пустым значением. Поле, содержащее строку"0", не пройдёт проверкуempty(). Для полей, где0является допустимым значением, используйте явное сравнение:if ($value === "").- Проверяйте тип до того, как доверять ему. Значения в
$_POSTвсегда являются строками. Используйтеfilter_var($n, FILTER_VALIDATE_INT)илиis_numeric()вместо того, чтобы предполагать, что пришло число. - Экранируйте при выводе, а не при сохранении. Храните в базе данных исходное (прошедшее валидацию) значение и применяйте
htmlspecialchars()только при вставке в HTML. Экранирование перед сохранением испортит данные для не-HTML-использования. - Используйте оператор объединения с null.
$_POST["x"] ?? ""позволяет избежать предупреждений «Undefined array key», когда поле отсутствует.
Валидация других типов полей
Тот же паттерн применяется к любому полю — меняется только правило валидации:
<?php
// Age: an integer between 18 and 120
$age = filter_var($_POST["age"] ?? "", FILTER_VALIDATE_INT, [
"options" => ["min_range" => 18, "max_range" => 120],
]);
if ($age === false) {
echo "Please enter an age between 18 and 120.";
}
// Username: 3-16 letters, digits, or underscores
$username = trim($_POST["username"] ?? "");
if (!preg_match('/^[A-Za-z0-9_]{3,16}$/', $username)) {
echo "Username must be 3-16 letters, digits, or underscores.";
}
?>Для правил, специфичных для URL и email, смотрите PHP Form URL & E-mail; для обязательных полей — PHP Form Required Fields; а чтобы увидеть все части собранными вместе — PHP Complete Form.
Заключение
Серверная валидация в PHP делает форму надёжной: она подтверждает наличие и правильность формата данных, а в сочетании с htmlspecialchars() при выводе и подготовленными запросами при сохранении — защищает от XSS и SQL-инъекций. Основной рецепт всегда одинаков: санитизация, валидация, сбор ошибок и повторное отображение формы с понятными сообщениями при наличии проблем. Освоив этот цикл, вы сможете проверять любое поле, просто подставив другое правило.