Способы доступа к данным запроса в PHP
Получение данных HTTP-запроса в PHP
Как безопасно извлечь данные запроса с фильтрацией и валидацией?
Наиболее эффективный подход - использование функций filter_input и filter_var. Они позволяют получить значение из указанного источника (GET, POST, SERVER и т.д.) и одновременно применить фильтр очистки или валидации. Это снижает риск XSS-атак и инъекций.
<?php
// Получаем параметр 'name' из GET-строки, удаляем теги
$name = filter_input(INPUT_GET, 'name', FILTER_SANITIZE_STRING);
// Получаем email из POST, проверяем корректность формата
$email = filter_input(INPUT_POST, 'email', FILTER_VALIDATE_EMAIL);
if ($email === false) {
echo 'Некорректный email';
} else {
echo "Привет, $name! Твой email: $email";
}
?>
Пояснение: filter_input первым аргументом принимает тип входа (INPUT_GET, INPUT_POST, INPUT_COOKIE, INPUT_SERVER, INPUT_ENV). Второй - имя ключа. Третий - константа фильтра. Если значение отсутствует, функция возвращает null; если не проходит валидацию - false. Это позволяет сразу обработать ошибочные ситуации.
Типичные проблемы: Фильтр FILTER_SANITIZE_STRING объявлен устаревшим в PHP 8.1+. Рекомендуется использовать FILTER_UNSAFE_RAW с последующей обработкой через htmlspecialchars при выводе. Также легко забыть указать фильтр, что сводит защиту к нулю. Решение: всегда задавать явный фильтр или комбинировать с filter_var после получения сырых данных.
Цель и случаи использования: Подходит для любых сценариев, где требуется первичная очистка входных данных - обработка форм, параметров URL, кук. Особенно рекомендуется в проектах без фреймворка для быстрой и безопасной работы.
Как получить параметры из строки запроса или тела формы напрямую?
Прямое обращение к суперглобальным массивам $_GET и $_POST - самый простой способ.
<?php
$id = $_GET['id'] ?? null;
$password = $_POST['password'] ?? '';
?>
Пояснение: Массивы заполняются автоматически: $_GET - параметры из URL (после ?), $_POST - данные из тела запроса с Content-Type: application/x-www-form-urlencoded или multipart/form-data. Оператор ?? задаёт значение по умолчанию, если ключ отсутствует, предотвращая Notice.
Типичные ошибки: Если не использовать ??, при отсутствии ключа возникнет Notice: Undefined index. Кроме того, данные никак не фильтруются - их вывод без экранирования ведёт к XSS. Решение: всегда экранировать через htmlspecialchars при выводе в HTML.
Цель и случаи использования: Подходит для быстрых прототипов, внутренних скриптов, когда безопасность не критична или данные обрабатываются дополнительно.
Как получить данные, отправленные в формате JSON или XML?
Когда клиент отправляет данные с Content-Type: application/json (например, из JavaScript fetch или SPA), суперглобальный $_POST не заполняется. Используется поток php://input.
<?php
$raw = file_get_contents('php://input');
$data = json_decode($raw, true); // true для ассоциативного массива
if ($data === null && json_last_error() !== JSON_ERROR_NONE) {
http_response_code(400);
echo 'Ошибка разбора JSON';
exit;
}
$name = $data['name'] ?? 'Гость';
echo "Привет, $name";
?>
Пояснение: php://input возвращает сырое тело запроса, доступен только для POST, PUT, PATCH, DELETE. Для форм multipart/form-data он недоступен. После получения сырых данных их нужно декодировать в зависимости от типа контента. json_decode с флагом true преобразует в массив.
Типичные проблемы: Если данные не являются корректным JSON, json_decode вернёт null, а json_last_error укажет на ошибку. Необходимо проверять результат. Также php://input можно прочитать только один раз - при повторном вызове вернёт пустую строку. Решение: сохранить результат в переменную.
Цель и случаи использования: Используется в REST API, вебхуках, одностраничных приложениях (SPA), где данные передаются в формате JSON или XML.
Как получить данные без указания источника (GET, POST, COOKIE)?
Суперглобальный массив $_REQUEST объединяет $_GET, $_POST и $_COOKIE (порядок определяется директивой request_order).
<?php
$action = $_REQUEST['action'] ?? 'default';
?>
Пояснение: Удобно, когда не важно, откуда пришёл параметр. Однако из-за возможного переопределения (GET может перезаписать POST, если стоит первым в порядке) это снижает безопасность.
Типичная проблема: Злоумышленник может передать параметр через URL, который перезапишет значение из POST, что может нарушить логику. Решение: избегать $_REQUEST в критических операциях (авторизация, платежи).
Цель и случаи использования: Подходит для простых скриптов, где источник не важен, например, для поиска или фильтрации.
Как получить заголовки HTTP-запроса, например Authorization или User-Agent?
Заголовки запроса можно получить через функцию getallheaders() или через $_SERVER с префиксом HTTP_.
<?php
// Способ 1: getallheaders()
$headers = getallheaders();
$auth = $headers['Authorization'] ?? '';
// Способ 2: $_SERVER
$userAgent = $_SERVER['HTTP_USER_AGENT'] ?? 'Неизвестно';
?>
Пояснение: getallheaders() возвращает ассоциативный массив всех заголовков (ключи в том же регистре, что и в запросе). $_SERVER['HTTP_*'] содержит заголовки, преобразованные в верхний регистр и с заменой дефисов на подчёркивания. Например, User-Agent становится HTTP_USER_AGENT.
Типичные ошибки: getallheaders() работает только в окружении, где поддерживается SAPI (не во всех CGI-режимах). В таких случаях используют $_SERVER. Также не все заголовки попадают в $_SERVER (например, Authorization может быть недоступен при аутентификации через Basic). Решение: комбинировать оба метода или использовать PSR-7.
Цель и случаи использования: Аутентификация (Bearer токены), проверка User-Agent, работа с CORS, получение реферера.
Как получить данные запроса в объектно-ориентированном стиле с использованием PSR-7?
Для современных приложений, особенно построенных на фреймворках, рекомендуется использовать реализацию PSR-7, например laminas/laminas-diactoros или nyholm/psr7. Библиотека создаёт объект ServerRequestInterface из глобальных переменных.
<?php
require 'vendor/autoload.php';
use Laminas\Diactoros\ServerRequestFactory;
$request = ServerRequestFactory::fromGlobals();
$queryParams = $request->getQueryParams();
$parsedBody = $request->getParsedBody();
$headers = $request->getHeaders();
$method = $request->getMethod();
$name = $queryParams['name'] ?? $parsedBody['name'] ?? 'Гость';
echo "Метод: $method, имя: $name";
?>
Пояснение: fromGlobals() автоматически заполняет объект данными из $_GET, $_POST, $_FILES, $_COOKIE, $_SERVER и php://input. После этого доступ к параметрам становится единообразным через методы интерфейса.
Типичные проблемы: Требуется установка сторонней библиотеки через Composer, что излишне для очень простых скриптов. Также может возникнуть путаница с обработкой multipart-форм - данные не попадают в getParsedBody(), а лежат в getUploadedFiles(). Решение: всегда проверять наличие соответствующих методов.
Цель и случаи использования: Крупные проекты, микросервисы, приложения, следующие стандартам PHP-FIG. Упрощает тестирование и замену компонентов.
Расширенные примеры получения данных в PHP
Пример 1: Получение JSON из POST-запроса с валидацией и обработкой ошибок
<?php
// Получаем сырые данные
$raw = file_get_contents('php://input');
if (empty($raw)) {
http_response_code(400);
exit('Пустое тело запроса');
}
// Декодируем JSON
$data = json_decode($raw, true);
$jsonError = json_last_error();
if ($jsonError !== JSON_ERROR_NONE) {
http_response_code(400);
echo 'Ошибка JSON: ' . json_last_error_msg();
exit;
}
// Проверяем обязательные поля
if (!isset($data['email']) || !isset($data['password'])) {
http_response_code(422);
echo 'Требуются поля email и password';
exit;
}
// Валидация email
$email = filter_var($data['email'], FILTER_VALIDATE_EMAIL);
if ($email === false) {
http_response_code(422);
echo 'Некорректный email';
exit;
}
echo "Email: $email, пароль получен";
?>
При отправке POST /api/login с телом {"email":"user@test.com","password":"123"}
Вывод: Email: user@test.com, пароль получен
При отправке с невалидным JSON: Ошибка JSON: Syntax error
Пример 2: Получение данных из PUT/PATCH запроса через php://input
<?php
$method = $_SERVER['REQUEST_METHOD'];
if ($method !== 'PUT' && $method !== 'PATCH') {
http_response_code(405);
exit('Допустимы только PUT и PATCH');
}
parse_str(file_get_contents('php://input'), $putData);
// $putData теперь содержит параметры из тела запроса в виде application/x-www-form-urlencoded
$name = $putData['name'] ?? '';
echo "Обновляем имя: $name";
?>
curl -X PUT -d "name=НовоеИмя" http://example.com/user/1 Вывод: Обновляем имя: НовоеИмя Обратите внимание: для JSON используйте json_decode, а не parse_str.
Пример 3: Использование filter_input с пользовательским callback-фильтром
<?php
// Определяем кастомный фильтр: удаляем лишние пробелы и приводим к нижнему регистру
$sanitizeName = function($value) {
return strtolower(trim(strip_tags($value)));
};
$name = filter_input(INPUT_POST, 'username', FILTER_CALLBACK, ['options' => $sanitizeName]);
if ($name === null) {
echo 'Параметр username не передан';
} else {
echo "Отфильтрованное имя: $name";
}
?>
POST username= <b>John</b> Вывод: Отфильтрованное имя: john
Пример 4: Получение загруженных файлов через $_FILES с проверкой типа
<?php
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
exit('Только POST');
}
$file = $_FILES['avatar'] ?? null;
if (!$file || $file['error'] !== UPLOAD_ERR_OK) {
http_response_code(400);
exit('Ошибка загрузки файла');
}
$allowedTypes = ['image/jpeg', 'image/png', 'image/gif'];
$finfo = finfo_open(FILEINFO_MIME_TYPE);
$detectedType = finfo_file($finfo, $file['tmp_name']);
finfo_close($finfo);
if (!in_array($detectedType, $allowedTypes)) {
http_response_code(415);
exit('Неподдерживаемый тип файла');
}
$dest = __DIR__ . '/uploads/' . basename($file['name']);
move_uploaded_file($file['tmp_name'], $dest);
echo "Файл сохранён: $dest";
?>
Форма: <input type="file" name="avatar"> Загрузка JPEG-файла: Файл сохранён: /var/www/uploads/photo.jpg
Пример 5: Комбинирование GET, POST и заголовков в одном запросе
<?php
// Получаем токен авторизации из заголовка
$authHeader = $_SERVER['HTTP_AUTHORIZATION'] ?? '';
if (!preg_match('/^Bearer\s+(\S+)$/', $authHeader, $matches)) {
http_response_code(401);
exit('Требуется Bearer токен');
}
$token = $matches[1];
// Получаем ID из GET
$id = filter_input(INPUT_GET, 'id', FILTER_VALIDATE_INT);
if (!$id) {
http_response_code(400);
exit('Неверный ID');
}
// Получаем новые данные из POST (JSON)
$input = json_decode(file_get_contents('php://input'), true);
$newName = $input['name'] ?? '';
echo "Пользователь $id будет обновлён. Токен: ...$token..., новое имя: $newName";
?>
curl -X PATCH \
-H "Authorization: Bearer secret123" \
-H "Content-Type: application/json" \
-d '{"name":"Новое имя"}' \
'http://example.com/api/user?id=5'
Вывод: Пользователь 5 будет обновлён. Токен: ...secret123..., новое имя: Новое имя
Пример 6: Получение информации о запросе через $_SERVER (метод, IP, URI)
<?php
$method = $_SERVER['REQUEST_METHOD'];
$uri = $_SERVER['REQUEST_URI'];
$ip = $_SERVER['REMOTE_ADDR'] ?? '127.0.0.1';
$port = $_SERVER['REMOTE_PORT'] ?? 0;
$agent = $_SERVER['HTTP_USER_AGENT'] ?? 'Unknown';
header('Content-Type: text/plain');
echo "Метод: $method\nURI: $uri\nIP: $ip\nПорт: $port\nUser-Agent: $agent";
?>
Пример вывода: Метод: GET URI: /test?foo=bar IP: 192.168.1.1 Порт: 54321 User-Agent: Mozilla/5.0 ...
Пример 7: Парсинг строки запроса с parse_str для нестандартных случаев
<?php
$queryString = 'key=value&arr[]=a&arr[]=b&special=%26%3D';
parse_str($queryString, $output);
print_r($output);
// Эквивалентно разбору $_SERVER['QUERY_STRING']
?>
Array
(
[key] => value
[arr] => Array
(
[0] => a
[1] => b
)
[special] => &=
)
Пример 8: Использование библиотеки guzzlehttp/psr7 для создания серверного запроса
<?php
require 'vendor/autoload.php';
use GuzzleHttp\Psr7\ServerRequest;
$request = ServerRequest::fromGlobals();
// Получаем все параметры запроса
$params = $request->getQueryParams() + (array)$request->getParsedBody();
$method = $request->getMethod();
$header = $request->getHeaderLine('Content-Type');
echo "Метод: $method\n";
echo "Content-Type: $header\n";
echo "Параметры: ";
print_r($params);
?>
Пример вывода при POST с form-data: Метод: POST Content-Type: multipart/form-data; boundary=... Параметры: Array ( [name] => Иван )