Что такое SAPI
SAPI — это слой, через который PHP подключён к среде выполнения. Аббревиатура обычно раскрывается как Server API. Один и тот же файл index.php может запускаться через PHP-FPM за Nginx, как Apache module, через CGI/FastCGI, из CLI или через встроенный сервер php -S. Язык остаётся тем же, но входные данные, модель процесса, доступные переменные и поведение вывода зависят от SAPI.
Для веб-приложения SAPI — граница между HTTP-миром и вашим PHP-кодом. Веб-сервер принимает запрос, передаёт его PHP-рантайму, PHP создаёт окружение выполнения, заполняет предопределённые переменные, запускает скрипт и отдаёт заголовки с телом ответа обратно наружу. Поэтому тема напрямую связана с Версии PHP и режимы выполнения, HTTP-заголовки, ответы и редиректы и более современным объектным слоем из PSR-7, middleware и HTTP-клиенты.
flowchart TD
A[Браузер или HTTP-клиент] --> B[Веб-сервер: Nginx / Apache]
B --> C[SAPI: PHP-FPM / Apache module / CGI]
C --> D[PHP runtime создает окружение запроса]
D --> E[Заполняются $_SERVER, $_GET, $_POST, $_FILES, $_COOKIE]
E --> F[index.php / front controller]
F --> G[Приложение валидирует input и строит response]
G --> H[headers + body]
H --> B
B --> I[HTTP-ответ клиенту]Request lifecycle в классическом PHP
В типичном PHP-FPM setup запрос проходит примерно такой путь: браузер отправляет HTTP-запрос в Nginx, Nginx решает, что URL должен обслуживать PHP, передаёт данные в FPM-процесс, PHP запускает нужный скрипт, а приложение читает вход через суперглобалы и пишет ответ через echo, header() и http_response_code().
Важная деталь: PHP-программа не «слушает порт» сама по себе, если речь не о специальных рантаймах или встроенном dev-сервере. В классической модели PHP-код живёт внутри одного запроса. После завершения скрипта локальные переменные исчезают, а следующий запрос получает новое окружение. Это отличается от долгоживущих воркеров, о которых отдельно говорят PHP-FPM, RoadRunner и долгоживущие воркеры и Swoole и OpenSwoole.
Минимальная диагностика текущего режима:
<?php
declare(strict_types=1);
echo PHP_SAPI . PHP_EOL;В CLI это часто выведет cli, под PHP-FPM — fpm-fcgi, во встроенном сервере — cli-server. Не стоит строить бизнес-логику на точном значении без необходимости, но для bootstrap-кода, диагностики и разных entrypoint'ов это полезно.
Суперглобалы: что это за переменные
Суперглобалы — встроенные массивы, доступные в любой области видимости без global. Например, внутри функции можно обратиться к $_GET['page'], и PHP не потребует передавать $_GET явно.
Главные суперглобалы веб-слоя:
$_SERVER // данные сервера, SAPI и окружения запроса
$_GET // query string: ?page=2&sort=date
$_POST // form data из POST-запросов
$_FILES // данные загруженных файлов
$_COOKIE // cookies из HTTP-запроса
$_SESSION // данные сессии после session_start()
$_REQUEST // смесь GET/POST/COOKIE по настройкам PHP
$_ENV // переменные окружения, если они импортируются
$GLOBALS // ссылки на глобальные переменныеУдобство суперглобалов — одновременно и проблема. Они доступны отовсюду, значит любой код может незаметно прочитать внешний ввод. Для маленького скрипта это нормально; для приложения лучше изолировать чтение суперглобалов в одном месте, а дальше работать с явными объектами или массивами.
$_SERVER: не только сервер
$_SERVER содержит смесь данных: имя скрипта, путь, метод запроса, query string, server name, remote address, protocol, а также HTTP-заголовки, которые SAPI часто кладёт в ключи вида HTTP_USER_AGENT или HTTP_ACCEPT.
<?php
$method = $_SERVER['REQUEST_METHOD'] ?? 'GET';
$uri = $_SERVER['REQUEST_URI'] ?? '/';
$userAgent = $_SERVER['HTTP_USER_AGENT'] ?? '';Название $_SERVER может обмануть: часть значений приходит от клиента или прокси. HTTP_USER_AGENT, HTTP_REFERER, HTTP_X_FORWARDED_FOR, многие HTTP_*-ключи нельзя считать доверенными. Даже IP-адрес за reverse proxy требует отдельной настройки доверенных прокси; просто взять первый X-Forwarded-For — плохая идея.
Ещё одна практическая осторожность: не все ключи гарантированы во всех SAPI и веб-серверах. CLI-запуск не даст нормальный REQUEST_METHOD, а разные конфигурации Apache, Nginx, FPM и контейнеров могут по-разному наполнять окружение. Поэтому почти всегда нужен ??, явная проверка или объект запроса, который нормализует данные.
$_GET, $_POST и $_REQUEST
$_GET — это распарсенная query string. Она не означает, что HTTP-метод был GET: у POST-запроса тоже может быть URL вида /search?q=php. $_POST обычно заполняется для HTML form data, например application/x-www-form-urlencoded и multipart/form-data. Если клиент отправляет JSON (Content-Type: application/json), тело запроса обычно читают отдельно через php://input, а затем декодируют через json_decode(); это уже ближе к теме JSON, сериализация и форматы данных.
$_REQUEST выглядит удобно, но в реальном приложении часто вреден. По умолчанию он собирается из нескольких источников, а точный состав и порядок зависят от request_order и variables_order в конфигурации PHP. Если в query string пришло role=user, а в cookie есть role=admin, код на $_REQUEST['role'] хуже читается и труднее проверяется. Явно выбирайте источник: $_GET для параметров URL, $_POST для form submit, $_COOKIE для cookies.
Подробная фильтрация ввода вынесена в GET, POST и фильтрация ввода, но базовое правило здесь уже важно: суперглобалы — это внешний ввод. Его валидируют, приводят к ожидаемому типу и только потом используют.
Cookies, sessions и files
$_COOKIE содержит cookies, которые браузер прислал в текущем запросе. Изменение $_COOKIE['theme'] = 'dark' не отправляет новую cookie пользователю; для этого нужен setcookie() до вывода тела ответа. Подробности — в Куки и сессии.
$_SESSION — тоже суперглобал, но сессия не становится полезной сама по себе. Обычно сначала вызывают session_start(): PHP читает session ID из cookie или URL-механизма, открывает session storage и наполняет $_SESSION. Здесь уже появляются вопросы fixation, regeneration, SameSite, HttpOnly и Secure, поэтому сессии нельзя воспринимать как «безопасное хранилище по умолчанию».
$_FILES появляется при upload через multipart form. В нём важны не только name и tmp_name, но и error, size, тип, лимиты PHP и веб-сервера. Загруженный файл сначала лежит во временном месте, и приложение должно проверить ошибку, размер, допустимый MIME/расширение и перенести файл в безопасное хранилище. Это отдельная тема Загрузка файлов, связанная с Файловая система и stream wrappers.
Почему superglobals стоит изолировать
Плохой вариант — читать $_POST, $_SERVER и $_SESSION прямо из сервисов, репозиториев и шаблонов. Такой код трудно тестировать: чтобы проверить метод, надо подменять глобальное состояние. Ещё хуже, если одна функция неявно зависит от текущего HTTP-запроса, хотя по названию выглядит как обычная доменная логика.
Лучше сделать тонкий слой на входе: front controller или controller читает суперглобалы, нормализует данные и передаёт дальше явный объект.
<?php
declare(strict_types=1);
final class HttpInput
{
public function __construct(
public readonly string $method,
public readonly string $path,
/** @var array<string, string> */
public readonly array $query,
) {}
}
function inputFromGlobals(): HttpInput
{
$method = strtoupper($_SERVER['REQUEST_METHOD'] ?? 'GET');
$path = parse_url($_SERVER['REQUEST_URI'] ?? '/', PHP_URL_PATH) ?: '/';
return new HttpInput(
method: $method,
path: $path,
query: array_map('strval', $_GET),
);
}
$input = inputFromGlobals();
if ($input->method === 'GET' && $input->path === '/search') {
$q = trim($input->query['q'] ?? '');
// Дальше приложение работает с $q, а не с $_GET напрямую.
}Это не полноценная замена PSR-7, но направление то же: превратить неявное окружение запроса в явную структуру. В проектах на middleware обычно используют ServerRequestInterface, ResponseInterface и streams из PSR-7, middleware и HTTP-клиенты. В небольшом проекте достаточно своего простого input DTO, если он держит границу чистой.
Практическая карта
Для справки полезно держать в голове такую раскладку: $_SERVER отвечает на вопрос «в каком окружении и каким HTTP-запросом меня вызвали», $_GET — «что было в URL», $_POST — «что пришло из form body», $_FILES — «какие upload-части были переданы», $_COOKIE — «какие cookies прислал клиент», $_SESSION — «что PHP достал из session storage», $_REQUEST — «смешанный shortcut, которого лучше избегать в серьёзном коде».
Суперглобалы — нормальная часть PHP, а не легаси-срам. Проблема начинается, когда они расползаются по всему приложению. Держите их на краю системы, валидируйте вход, явно передавайте данные дальше — и классический PHP request-response код становится предсказуемым, тестируемым и совместимым с более современными HTTP-абстракциями.
См. также
- Версии PHP и режимы выполнения — где CLI, FPM и другие режимы вписываются в жизненный цикл PHP.
- GET, POST и фильтрация ввода — как валидировать query string и form data.
- Куки и сессии — session ID, flags cookies и границы надёжности PHP sessions.
- Загрузка файлов — как безопасно обрабатывать
$_FILES. - HTTP-заголовки, ответы и редиректы — как PHP отправляет headers и status code.
- PSR-7, middleware и HTTP-клиенты — объектная модель HTTP-запроса и ответа поверх SAPI.
- CLI и встроенный сервер — чем CLI SAPI отличается от веб-запуска.