Валидация и фильтрация входных данных в PHP без дыр
filter_var, приведение типов и параметризованные запросы — как проверять данные в PHP 8.x так, чтобы форма не пропускала мусор, а база не ловила инъекции.

Задача, в которую упирается любой обработчик формы
Форма регистрации принимает email, возраст и ссылку на профиль. Очевидное решение — if (!empty($_POST['email'])) и вставка в запрос через конкатенацию. В итоге в базу попадает строка not-an-email, возраст -5, а поле ссылки содержит '; DROP TABLE users;--. Каждая из трёх проверок «на глаз» пропускает свой класс ошибок.
Разберём, как в PHP 8.1+ разделить две разные операции — валидацию (данные корректны или нет) и санитизацию (привести к безопасному виду) — и почему их нельзя путать. Инъекции в SQL здесь только упоминаются в связке с запросами: полный разбор защиты БД — отдельная тема.
Валидация и санитизация — это разные вещи
Их постоянно смешивают, и отсюда половина уязвимостей.
- Валидация отвечает на вопрос «корректны ли данные». Email — это email? Возраст — целое число в разумных границах? Результат — да или нет, входные данные при этом не меняются.
- Санитизация меняет данные: вырезает или экранирует опасные символы. Проблема в том, что «безопасный» зависит от контекста — для HTML одно, для SQL другое, для shell третье.
Санитизировать вход «на всякий случай» на этапе приёма — ошибка. Экранировать нужно на выходе, под конкретный контекст:htmlspecialcharsпри выводе в HTML, параметры PDO при вставке в базу. Иначе вы получите email видаo'brien@mail.comв базе и будете гадать, откуда это.
Почему filter_var, а не регулярки на email
Собственная регулярка для email почти всегда неправильная: спецификация RFC 5322 сложнее, чем кажется. В PHP есть встроенный фильтр, который покрывает подавляющее большинство случаев.
Три подхода к валидации входа
1. filter_var с фильтрами валидации
Базовый инструмент. Возвращает приведённое значение или false.
<?php
// PHP 8.1+
$email = filter_var($_POST['email'] ?? '', FILTER_VALIDATE_EMAIL);
if ($email === false) {
// email некорректен
}
// возраст: целое число в диапазоне 0..150
$age = filter_var($_POST['age'] ?? '', FILTER_VALIDATE_INT, [
'options' => ['min_range' => 0, 'max_range' => 150],
]);
if ($age === false) {
// не число либо вне диапазона
}
// URL: требуем схему http/https
$url = filter_var($_POST['url'] ?? '', FILTER_VALIDATE_URL);
if ($url === false || !in_array(parse_url($url, PHP_URL_SCHEME), ['http', 'https'], true)) {
// ссылка не подходит
}
Ловушка FILTER_VALIDATE_BOOLEAN: он возвращает null при провале, а не false, если добавить флаг FILTER_NULL_ON_FAILURE. Без флага строка «нет» вернёт false, и вы не отличите её от честного «false». Для булевых значений всегда используйте флаг и сравнивайте через ===.
2. filter_input вместо прямого обращения к $_POST
filter_input(INPUT_POST, 'email', FILTER_VALIDATE_EMAIL) берёт данные из суперглобала напрямую и не зависит от того, что кто-то раньше по коду перезаписал $_POST. На практике разница невелика, но filter_input честнее показывает намерение: мы валидируем именно пришедший запрос.
3. Приведение типов и явные проверки для сложных случаев
filter_var не покрывает всё. Дата, значение из фиксированного списка, вложенный массив — тут нужны свои проверки.
<?php
// PHP 8.1+
// значение из белого списка (enum-подход)
$role = $_POST['role'] ?? '';
$allowedRoles = ['user', 'editor', 'admin'];
if (!in_array($role, $allowedRoles, true)) {
$role = 'user'; // безопасное значение по умолчанию
}
// дата в формате Y-m-d, с проверкой на реальность даты
$raw = $_POST['birth'] ?? '';
$date = DateTimeImmutable::createFromFormat('Y-m-d', $raw);
$errors = DateTimeImmutable::getLastErrors();
$dateValid = $date !== false
&& ($errors === false || ($errors['warning_count'] === 0 && $errors['error_count'] === 0));
Проверка через in_array(..., true) с третьим аргументом true — строгое сравнение. Без него «0» совпадёт с 0 и с false, и белый список пропустит лишнее.
Про getLastErrors в PHP 8.2+
Начиная с PHP 8.2 getLastErrors() возвращает false, если ошибок не было (раньше — массив с нулевыми счётчиками). Поэтому в примере выше проверка учитывает оба случая. Если пишете под 8.1 и ниже, поведение отличается — сверьтесь с разделом DateTime в документации.
Что чем платит
| Подход | Что валидирует хорошо | Где не подходит |
|---|---|---|
| filter_var / filter_input | email, int, float, URL, IP, boolean | даты, enum, вложенные структуры, бизнес-правила |
| Приведение типов + in_array/сравнение | белые списки, простые диапазоны | сложные форматы, где регулярка неизбежна |
| DateTimeImmutable::createFromFormat | даты и время с проверкой на реальность | всё, что не дата |
| Библиотека валидации (Respect/Validation, Symfony Validator) | сложные правила, вложенные объекты, сообщения об ошибках | простой скрипт из трёх полей — избыточно |
Когда что выбирать
- Три-пять полей, один обработчик —
filter_varплюс пара явных проверок. Библиотека тут только раздувает зависимости. - API с десятками эндпоинтов и вложенными телами запросов — Symfony Validator или Respect/Validation: они дают декларативные правила и собранный список ошибок, а не десять вложенных
if. - Значение из фиксированного набора — всегда белый список и строгое сравнение, никогда чёрный список «запрещённых» символов. Чёрный список вы рано или поздно не дополните.
Где это ломается
Самые частые ошибки, которые проходят ревью и живут в проде:
- Санитизация вместо валидации. Разработчик применяет
FILTER_SANITIZE_EMAIL, получает «очищенную» строку и радуется. Но санитизация не проверяет корректность — она просто вырезает символы. На выходе может остаться мусор, который формально «прошёл». Начиная с PHP 8.1 многие фильтрыFILTER_SANITIZE_*помечены как устаревшие (например,FILTER_SANITIZE_STRINGудалён). Для строк используйте валидацию и экранирование на выводе. - Экранирование на входе вместо вывода.
htmlspecialcharsна этапе приёма портит данные: в базе окажется<вместо<, и при выводе в JSON вы получите двойное экранирование. - Доверие к Content-Type и заголовкам. Клиент присылает JSON, а вы читаете
$_POST— оно пустое, потому что PHP не парсит JSON-тело автоматически. Читайтеfile_get_contents('php://input')иjson_decodeс флагомJSON_THROW_ON_ERROR. - Отсутствие проверки на массив.
$_POST['email']может прийти массивом (email[]=a&email[]=b), иfilter_varвернётfalse, но некоторые проверки упадут с warning. Проверяйтеis_stringперед строковыми операциями.
<?php
// Чтение JSON-тела запроса, PHP 8.1+
$body = file_get_contents('php://input');
try {
$data = json_decode($body, true, 512, JSON_THROW_ON_ERROR);
} catch (JsonException $e) {
http_response_code(400);
exit('Некорректный JSON');
}
$email = filter_var($data['email'] ?? '', FILTER_VALIDATE_EMAIL);
Что настроить дальше
Валидация входа — только первый рубеж. Дальше по потоку данных:
- Запросы к базе — только через подготовленные выражения PDO с
bindValueили именованными плейсхолдерами. filter_var не защищает от SQL-инъекций, это разные задачи. - Вывод в HTML —
htmlspecialchars($value, ENT_QUOTES | ENT_HTML5, 'UTF-8')в момент вывода, а не при сохранении. - Загрузка файлов — проверка реального MIME-типа через
finfo, а не по расширению и не по$_FILES['...']['type'], которое присылает клиент.
Если проект дорос до десятков форм, посмотрите Symfony Validator: атрибуты валидации прямо на свойствах DTO читаются лучше, чем россыпь filter_var по контроллерам.
Инфобез и 152-ФЗ: Аудит политик конфиденциальности, правила сбора cookie.
Спросить за 20 ₽Источники: PHP Manual — Data Filtering (filter_var, фильтры), PHP Manual — DateTimeImmutable::createFromFormat
Частые вопросы
У меня PHP 7.4, примеры сработают?
Чем FILTER_VALIDATE_EMAIL отличается от FILTER_SANITIZE_EMAIL?
filter_var защищает от SQL-инъекций?
Почему мой JSON-запрос приходит с пустым $_POST?
Валидация не сработала, форма пропустила мусор — с чего начать?
Стоит ли ставить библиотеку валидации ради простой формы?
Материал носит информационный характер и подготовлен редакцией «Агентуры». Он не является офертой, рекламой или индивидуальной консультацией. Упомянутые продукты, компании и торговые знаки принадлежат их правообладателям. Перед принятием решений, влекущих юридические или финансовые последствия, обратитесь к профильному специалисту.