Формы и запросы (GET/POST)
PHP вырос на вебе, поэтому формы и запросы - его родная среда обитания. Но данные от пользователя - это всегда «подарок с сюрпризом».
GET и POST - разница
GET - параметры в URL (?q=php&page=2). Используй для поиска, фильтрации, пагинации - запросы, которые можно повторять и ставить в закладки. Базы методов и кодов - в кросс-треке «Основы HTTP».
POST - данные в теле запроса. Используй для логина, регистрации, отправки форм - действий, которые меняют состояние.
<?php
// GET: /search?q=php&page=2
$q = $_GET['q'] ?? '';
$page = (int)($_GET['page'] ?? 1);
// POST: форма логина
$email = $_POST['email'] ?? '';
$password = $_POST['password'] ?? '';
Суперглобальные массивы
PHP предоставляет несколько суперглобальных массивов для работы с запросами:
<?php
$_GET // параметры из URL (?key=value)
$_POST // данные из тела POST-запроса
$_REQUEST // объединение GET + POST + COOKIE
$_SERVER // информация о сервере и запросе
$_FILES // загруженные файлы
$_COOKIE // куки
$_SESSION // данные сессии
Полезные значения из $_SERVER:
<?php
$method = $_SERVER['REQUEST_METHOD']; // GET, POST, PUT...
$uri = $_SERVER['REQUEST_URI']; // /search?q=php
$ip = $_SERVER['REMOTE_ADDR']; // IP клиента
Валидация входных данных
Типичный паттерн валидации:
<?php
$errors = [];
$name = trim($_POST['name'] ?? '');
if ($name === '') {
$errors[] = 'Имя обязательно';
}
$email = trim($_POST['email'] ?? '');
if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
$errors[] = 'Некорректный email';
}
$age = $_POST['age'] ?? '';
if (!ctype_digit($age) || (int)$age < 1 || (int)$age > 150) {
$errors[] = 'Укажите корректный возраст';
}
if (empty($errors)) {
// данные валидны - обрабатываем
} else {
// показываем ошибки пользователю
}
filter_var и filter_input
PHP имеет встроенные фильтры для валидации и очистки:
<?php
// Валидация
$email = filter_var($_POST['email'] ?? '', FILTER_VALIDATE_EMAIL);
// Вернёт email или false
$url = filter_var($_POST['url'] ?? '', FILTER_VALIDATE_URL);
$age = filter_var($_POST['age'] ?? '', FILTER_VALIDATE_INT, [
'options' => ['min_range' => 1, 'max_range' => 150],
]);
// Вернёт int или false
// Очистка (sanitize) - убирает нежелательные символы
$clean = filter_var($input, FILTER_SANITIZE_SPECIAL_CHARS);
Альтернатива - filter_input (читает прямо из запроса):
<?php
$page = filter_input(INPUT_GET, 'page', FILTER_VALIDATE_INT) ?: 1;
Пример формы с валидацией
<?php
$errors = [];
$name = '';
$email = '';
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$name = trim($_POST['name'] ?? '');
$email = trim($_POST['email'] ?? '');
if ($name === '') {
$errors[] = 'Введите имя';
}
if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
$errors[] = 'Некорректный email';
}
if (empty($errors)) {
// Успех - обработка данных
echo '<p>Спасибо, ' . htmlspecialchars($name) . '!</p>';
}
}
?>
<form method='post'>
<label>Имя: <input name='name' value="<?= htmlspecialchars($name) ?>' /></label>
<label>Email: <input name='email' value='<?= htmlspecialchars($email) ?>" /></label>
<?php if (!empty($errors)): ?>
<ul style='color: red;'>
<?php foreach ($errors as $err): ?>
<li><?= htmlspecialchars($err) ?></li>
<?php endforeach; ?>
</ul>
<?php endif; ?>
<button type='submit'>Отправить</button>
</form>
Обрати внимание: value="<?= htmlspecialchars($name) ?>" сохраняет введённые данные после отправки - пользователю не нужно заполнять форму заново.
Загрузка файлов
<?php
// HTML-форма с enctype
// <form method="post" enctype="multipart/form-data">
// <input type="file" name="avatar" />
// </form>
if (isset($_FILES['avatar']) && $_FILES['avatar']['error'] === UPLOAD_ERR_OK) {
$tmp = $_FILES['avatar']['tmp_name'];
$name = $_FILES['avatar']['name'];
$size = $_FILES['avatar']['size'];
// Проверки безопасности
$maxSize = 2 * 1024 * 1024; // 2MB
if ($size > $maxSize) {
die('Файл слишком большой');
}
$allowed = ['image/jpeg', 'image/png', 'image/gif'];
$mime = mime_content_type($tmp);
if (!in_array($mime, $allowed, true)) {
die('Недопустимый тип файла');
}
// Безопасное имя файла
$ext = pathinfo($name, PATHINFO_EXTENSION);
$safeName = bin2hex(random_bytes(16)) . '.' . $ext;
move_uploaded_file($tmp, __DIR__ . '/uploads/' . $safeName);
}
PRG-паттерн (Post/Redirect/Get)
Если пользователь обновит страницу после POST, данные отправятся повторно. Решение - редирект после успешной обработки:
<?php
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$name = trim($_POST['name'] ?? '');
// ... валидация и обработка ...
if (empty($errors)) {
// Сохраняем результат в сессию
$_SESSION['flash'] = "Спасибо, $name!";
// Редирект - браузер сделает GET
header('Location: /success.php');
exit;
}
}
CSRF-защита
Без защиты злоумышленник может создать страницу, которая отправит форму от имени пользователя. Решение - токен в сессии:
<?php
session_start();
// Генерация токена (один раз)
if (empty($_SESSION['csrf_token'])) {
$_SESSION['csrf_token'] = bin2hex(random_bytes(32));
}
// В форме - скрытое поле
// <input type="hidden" name="csrf_token" value="<?= $_SESSION['csrf_token'] ?>" />
// При обработке - проверка
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
if (!hash_equals($_SESSION['csrf_token'], $_POST['csrf_token'] ?? '')) {
http_response_code(403);
die('Недействительный CSRF-токен');
}
// ... обработка формы ...
}
Типичные ошибки
- Форма без CSRF-токена. Любой сторонний сайт может отправить POST от имени залогиненного пользователя. Добавь
<input type="hidden" name="_token">и проверяй черезhash_equals. В Symfony токен встроен в Form Component. - Путаница sanitize и validate.
FILTER_SANITIZE_*молча режет символы и пропускает «почти валидные» данные дальше, а тебе нужно отвергнуть невалидный ввод. СначалаFILTER_VALIDATE_*- отвергни, потом sanitize/escape только перед использованием. - Слепое доверие
$_POST.$_POST['age']- это всегда строка, а может и массив, если клиент прислалage[]=1&age[]=2. Приводи к нужному типу черезfilter_varсFILTER_VALIDATE_INTи проверяй границыmin_range/max_range. - Повторная отправка формы при F5. Без PRG-паттерна обновление страницы дублирует заказ или комментарий. После успешного POST делай
header('Location: ...'); exit;- браузер перейдёт по GET и F5 безопасен. echo $_POST['name']безhtmlspecialchars. Пользователь вставит<script>- получишь XSS на всех, кто откроет страницу. Экранируй каждый вывод:htmlspecialchars($v, ENT_QUOTES, 'UTF-8'), в том числе внутри атрибутовvalue="".
Best practices
- Один CSRF-токен на сессию, проверка через
hash_equals-===уязвим к timing-атакам. - Валидируй сначала, sanitize/escape - только на границе вывода (HTML, SQL, shell). Это две разные задачи.
- Всегда приводи
$_POST/$_GETк ожидаемому типу черезfilter_varи проверяй границы - никаких голых(int)$_POST[...]. - Любой успешный POST завершай редиректом (PRG), флеш-сообщение храни в
$_SESSION- F5 не продублирует операцию. - Файлы из
$_FILESпроверяй поmime_content_type, размеру и whitelist расширений; имя генерируй сам черезbin2hex(random_bytes(16)). В реальных проектах используй Symfony Validator (File,Imageconstraints) вместо ручной логики.
Мини-задание
- Сделай форму «Калькулятор»: два числа + операция (через select)
- Валидируй ввод через
filter_varсFILTER_VALIDATE_INT, выводи ошибки - Сохраняй введённые значения в форме через
htmlspecialchars - Добавь CSRF-токен к форме
- Реализуй загрузку аватара с проверкой типа и размера файла