Ответ
Фильтрация (санация) и валидация данных должны происходить на нескольких уровнях для обеспечения безопасности и целостности данных (Defense in Depth).
1. Уровень представления (клиентская сторона):
- HTML5-валидация (
required,type="email",pattern) для улучшения UX. - JavaScript-валидация для мгновенной обратной связи.
- Важно: Это ненадежно, так как легко отключается, и служит только для удобства пользователя.
2. Уровень транспорта (сервер, точка входа): Это ключевой уровень. Входящие данные (HTTP-запросы) должны быть проверены и очищены перед передачей в бизнес-логику.
- Валидация: Проверка формата, обязательности, диапазонов, корректности бизнес-правил.
-
Фильтрация: Очистка от потенциально опасных символов (XSS).
// Пример с использованием Symfony Validator и санитизатора class UserRegistrationData { #[AssertNotBlank] #[AssertEmail] private string $email; #[AssertNotBlank] #[AssertLength(min: 8, max: 50)] #[AssertRegex(pattern: '/^(?=.*[a-z])(?=.*[A-Z])(?=.*d).+$/')] private string $password; } // В контроллере $data = new UserRegistrationData(); $data->email = filter_var($request->get('email'), FILTER_SANITIZE_EMAIL); $data->password = $request->get('password'); // Пароли не санитизируют $errors = $validator->validate($data); if (count($errors) > 0) { ... }
3. Уровень бизнес-логики (доменный слой):
- Инварианты предметной области проверяются внутри сущностей (Entities) или доменных сервисов.
class Order { private function __construct(private float $amount) { if ($amount <= 0) { throw new DomainException('Order amount must be positive.'); } } }
4. Уровень persistence (база данных):
- Использование подготовленных выражений (Prepared Statements) для защиты от SQL-инъекций.
- Ограничения на уровне схемы БД (
NOT NULL,UNIQUE,CHECK).
5. Уровень вывода (View/Template):
- Экранирование (escaping) всех пользовательских данных при выводе в HTML, JSON, XML для защиты от XSS.
// В шаблоне Twig экранирование включено по умолчанию <p>Welcome, {{ user.username }}!</p>
Итог: Валидация и фильтрация — это непрерывный процесс, а не одноразовое действие в контроллере.
Ответ 18+ 🔞
Да ты послушай, как эту всю кухню безопасности выстраивать надо, а то некоторые думают, что один htmlspecialchars() и — всё, приехали. Нет, чувак, тут слоёв, как в хорошем торте, овердохуища.
Первый слой — это лицо для лохов (клиентская сторона).
Ты там на фронте навешаешь required, type="email" — пользователю удобно, сразу видно, где он накосячил. JavaScript'ом подхватишь, мгновенную отдачу дашь. Но это всё, ёпта, мартышлюшка! Любой школьник в DevTools это отключит и пошлёт тебе в запросе что угодно. Доверия к этому — ноль, ебать. Только для уюта.
Второй слой — это серьёзно, въездной контроль (сервер). Вот тут уже терпения ноль, ебать, и волнение ебать начинается. Каждый запрос, как подозрительный тип на границе: его надо обыскать, проверить документы и отмыть, если надо.
- Валидация: А че там по паспорту? Формат почты верный? Пароль не из трёх символов? Возраст не отрицательный? Бизнес-правила не нарушает?
- Фильтрация (Санация): А не принёс ли он в кармане гвоздь (скрипт на XSS)? Выковыриваем.
// Допустим, на Symfony делаем. Создаём объект-контейнер для данных.
class UserRegistrationData
{
#[AssertNotBlank] // Не пустое, бля!
#[AssertEmail] // Это реально почта?
private string $email;
#[AssertNotBlank]
#[AssertLength(min: 8, max: 50)] // Не меньше восьми, не длиннее полтинника
#[AssertRegex(pattern: '/^(?=.*[a-z])(?=.*[A-Z])(?=.*d).+$/')] // Чтоб и буквы, и цифры, ядрёна вошь!
private string $password;
}
// В контроллере принимаем
$data = new UserRegistrationData();
// Почту чистим от мусора
$data->email = filter_var($request->get('email'), FILTER_SANITIZE_EMAIL);
// Пароль НЕ чистим! Иначе он "123&<script>" превратится в "123", и пользователь потом не зайдёт. Храним как есть, но хэшируем.
$data->password = $request->get('password');
// А теперь валидатор долбит этот объект как прокурор
$errors = $validator->validate($data);
if (count($errors) > 0) {
// Всё, чувак, иди отсюда, документы не в порядке.
}
Третий слой — внутренние правила завода (бизнес-логика). Допустим, данные прошли КПП. Но у тебя в домене свои законы. Заказ не может быть на ноль или отрицательную сумму. Это проверяют сами сущности.
class Order {
private function __construct(private float $amount) {
if ($amount <= 0) {
// Какого хуя? Заказ на минус десять рублей?
throw new DomainException('Order amount must be positive.');
}
}
}
Четвёртый слой — сейф (База данных).
Всё, данные чистые и правильные. Теперь надо их сохранить так, чтобы сейф не взломали. Подготовленные выражения (Prepared Statements) — это наш бронежилет от SQL-инъекций. Плюс сама схема БД (NOT NULL, UNIQUE) — последний рубеж, если кто-то проскочил.
Пятый слой — упаковка на выход (Шаблоны). Ты данные из БД достал, теперь выводишь пользователю. СТОП! А вдруг там в имени пользователя, которое он когда-то ввёл, был скрипт? Мы его на втором слое могли и пропустить, если это было разрешено (тип, никнейм). Поэтому при выводе ВСЕГДА экранируем. В Twig, к счастью, это по умолчанию.
// Twig сам всё заэкранирует. Без паники.
<p>Welcome, {{ user.username }}!</p> // Если в username был "<script>", то увидим текст, а не скрипт.
Итог, чувак: Это не "поставил валидатор в контроллере и свободен". Это непрерывный конвейер, где на каждом этапе своя проверка. Называется Defense in Depth — глубокая эшелонированная оборона. Одна защита сломалась — есть вторая, третья. Так и живём, блядь.