API HTTP-запроса

OxPHP предоставляет объектно-ориентированный API для доступа к данным HTTP-запроса. Вместо чтения $_GET, $_POST, $_COOKIE, $_FILES и $_SERVER вы вызываете методы объекта Request, который возвращает ровно то, что вы запрашиваете, — не больше и не меньше.

Содержание

Обзор

oxphp_http_request() возвращает прокси только для чтения к данным HTTP-запроса, хранящимся в текущем потоке воркера. Данные загружаются лениво. Одиночный вызов метода вроде $request->header('Accept') обращается напрямую к структуре данных на стороне Rust и возвращает только это значение. Вызовы, возвращающие массив целиком, вроде $request->headers(), строят массив один раз и кэшируют его в PHP-объекте на время обработки запроса.

Зачем использовать его вместо суперглобальных переменных?

  • Разбор JSON-тела встроен. $request->payload() разбирает application/json, application/x-www-form-urlencoded и multipart/form-data без дополнительного кода.
  • Никаких опечаток в ключах массива. В $request->method() труднее ошибиться, чем в $_SERVER['REQUEST_METHOD'].
  • Определение типа загруженных файлов. $request->file('avatar')->type() возвращает MIME-тип, определённый по фактическому содержимому файла, а не по значению, переданному клиентом.
  • Тестируемость. Поскольку поведение задаётся интерфейсами, в модульных тестах можно внедрять mock-реализации.
  • Суперглобальные переменные остаются доступны. Установка SUPERGLOBALS_ENABLED=false необязательна. Объектный API работает в любом случае.

Получение объекта Request

php
<?php $request = oxphp_http_request();

Вызывайте oxphp_http_request() в любом месте скрипта, выполняющегося в рамках активного HTTP-запроса, в том числе внутри колбэка oxphp_worker():

php
<?php oxphp_worker(function () { $request = oxphp_http_request(); $method = $request->method(); // ... });

Методы RequestInterface

URI и метод

php
$request->method(): string

Возвращает HTTP-метод в верхнем регистре: "GET", "POST", "PUT", "PATCH", "DELETE" и т. д.

php
$request->isMethod(string $method): bool

Проверка метода без учёта регистра.

php
$request->path(): string

Путь URI без строки запроса: "/users/42".

php
$request->fullUri(): string

Полный URI, включая схему, хост, необязательный нестандартный порт, путь и строку запроса: "https://example.com:8080/users/42?page=2". Стандартные порты (80 для HTTP, 443 для HTTPS) опускаются.

php
$request->scheme(): string

"https" или "http".

php
$request->isSecure(): bool

true, если схема — "https".

php
$request->host(): string

Имя хоста из заголовка Host. Возвращает пустую строку, если заголовок отсутствует (запросы HTTP/1.0 без заголовка Host).

php
$request->port(): int

Порт из заголовка Host. Если он не указан явно, возвращает значение по умолчанию для схемы: 80 для HTTP, 443 для HTTPS.

За обратным прокси

scheme(), isSecure(), host() и port() учитывают X-Forwarded-Proto и X-Forwarded-Host, если удалённый узел (peer) входит в TRUSTED_PROXIES. Без доверенных прокси они отражают параметры прямого соединения.

php
$request->queryString(): ?string

Необработанная строка запроса без ведущего ?. Возвращает null, если строки запроса нет.

Протокол

php
$request->httpProtocol(): string

Полная строка протокола: "HTTP/1.1" или "HTTP/2".

php
$request->httpProtocolVersion(): string

Только номер версии: "1.1" или "2".

Параметры строки запроса

php
$request->query(?string $key = null, mixed $default = null): mixed

Доступ к параметрам строки запроса.

Вызов Возвращает
$request->query() Все параметры в виде массива, включая вложенные массивы
$request->query('page') Значение page или null, если отсутствует
$request->query('page', 1) Значение page или 1, если отсутствует

Синтаксис с квадратными скобками (?tags[]=php&tags[]=async) разбирается во вложенные массивы:

php
// Request: GET /search?q=oxphp&tags[]=php&tags[]=async $q = $request->query('q'); // "oxphp" $tags = $request->query('tags'); // ["php", "async"] $all = $request->query(); // ["q" => "oxphp", "tags" => ["php", "async"]]

Найденные значения всегда являются строками. $default возвращается как есть, если ключ отсутствует.

Разобранное тело запроса

php
$request->payload(?string $key = null, mixed $default = null): mixed

Возвращает разобранное тело запроса. Тело разбирается в соответствии с заголовком Content-Type:

Content-Type Возвращает
application/x-www-form-urlencoded Ассоциативный массив значений полей
multipart/form-data Ассоциативный массив значений текстовых полей
application/json Декодированный массив или скаляр; null при некорректном JSON
Любое другое значение null

payload() не ограничивается POST-запросами. Он работает с PUT, PATCH и любым другим методом, который отправляет тело. Разобранный результат кэшируется при первом вызове и переиспользуется на протяжении всего времени жизни запроса.

Вызов Возвращает
$request->payload() Всё разобранное тело запроса
$request->payload('email') Значение одного поля или null, если отсутствует
$request->payload('email', '') Значение одного поля или '', если отсутствует
php
<?php // JSON request: POST /api/users // Content-Type: application/json // Body: {"name": "Alice", "role": "admin"} $name = $request->payload('name'); // "Alice" $role = $request->payload('role'); // "admin" $data = $request->payload(); // ["name" => "Alice", "role" => "admin"]

Заголовки

php
$request->header(string $name, ?string $default = null): ?string

Возвращает необработанное значение заголовка. Имена заголовков нечувствительны к регистру. Для заголовков с несколькими значениями (Accept, X-Forwarded-For) вся строка заголовка возвращается как единая строка. Разбор — на вашей ответственности.

php
$request->hasHeader(string $name): bool

Возвращает true, если указанный заголовок присутствует.

php
$request->headers(): array

Возвращает все заголовки в виде ассоциативного массива. Каждый ключ — это имя заголовка в том виде, в каком оно получено (без нормализации), а каждое значение — необработанная строка заголовка.

php
<?php $accept = $request->header('Accept'); // "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8" if ($request->hasHeader('Authorization')) { $token = $request->header('Authorization'); } $all = $request->headers(); // ["Content-Type" => "application/json", "Accept" => "...", ...]

Cookies

php
$request->cookie(string $name, ?string $default = null): ?string

Возвращает значение одной cookie или $default, если cookie отсутствует.

php
$request->cookies(): array

Возвращает все cookies в виде ассоциативного массива пар имя-значение.

php
<?php $theme = $request->cookie('theme', 'light'); // "dark" or "light" $session = $request->cookie('session'); // null if absent $all = $request->cookies(); // ["theme" => "dark", ...]

Сырое тело запроса

php
$request->body(): string

Возвращает необработанные байты тела запроса. Это эквивалент file_get_contents('php://input') в OxPHP. В отличие от payload(), body() не кэшируется. Каждый вызов обращается к нижележащей структуре данных.

php
$request->contentType(): ?string

Возвращает значение заголовка Content-Type или null, если он отсутствует.

php
<?php // Read raw body for signature verification $raw = $request->body(); $signature = $request->header('X-Hub-Signature-256'); $valid = hash_hmac('sha256', $raw, $secret) === $signature;

body() и payload() независимы. Вы можете вызвать оба в рамках одного запроса.

Загрузка файлов

php
$request->file(string $name): ?UploadedFileInterface

Возвращает загруженный файл для указанного имени поля или null, если поле отсутствует. Для полей-массивов (name="photos[]") возвращает первый файл.

php
$request->files(?string $name = null): array
Вызов Возвращает
$request->files() Все загруженные файлы в виде плоского массива UploadedFileInterface
$request->files('photos') Все файлы для поля photos (поддерживает name="photos[]")
php
<?php $avatar = $request->file('avatar'); if ($avatar && $avatar->isValid()) { $mime = $avatar->type(); // Detected from file contents, not the client claim $name = $avatar->name(); // Original filename $avatar->moveTo('/var/uploads/' . basename($name)); } // Multiple files $photos = $request->files('photos'); // UploadedFileInterface[] foreach ($photos as $photo) { if ($photo->isValid()) { $photo->moveTo('/var/uploads/' . basename($photo->name())); } }

Клиент

php
$request->ip(): string

Возвращает IP-адрес клиента. Когда TRUSTED_PROXIES настроен и узел, от которого пришёл запрос, входит в доверенный набор, это самый правый недоверенный адрес из X-Forwarded-For или Forwarded (RFC 7239). В противном случае это IP непосредственного узла — как правило, вашего балансировщика нагрузки, а не конечного клиента.

Необработанный заголовок X-Forwarded-For по-прежнему доступен через $request->header('X-Forwarded-For') для нетривиальных случаев, но разбирать его вручную редко получается правильно (крайний левый или крайний правый адрес, отсутствие проверки доверия по CIDR). Вместо этого настройте TRUSTED_PROXIES. См. Доверенные прокси.

Тайминги

php
$request->startTime(bool $asFloat = false): int|float

Возвращает Unix-таймстамп момента получения этого запроса.

Вызов Возвращает
$request->startTime() Целое число секунд: 1711234567
$request->startTime(true) Число с плавающей точкой с субсекундной точностью: 1711234567.3412
php
<?php $elapsed = microtime(true) - $request->startTime(true); error_log(sprintf("Request took %.3fs so far", $elapsed));

Атрибуты

php
$request->attributes(): AttributesInterface

Возвращает изменяемый контейнер атрибутов для текущего запроса. Используйте атрибуты, чтобы разделять данные между middleware, обработчиками маршрутов и другим кодом в рамках одного запроса без использования глобальных переменных.

php
<?php // In authentication middleware $request->attributes()->set('user', $authenticatedUser); // In the route handler $user = $request->attributes()->get('user');

Атрибуты существуют в пределах одного запроса и сбрасываются при каждом новом запросе в режиме воркеров. При использовании файберов атрибуты являются общими для всех файберов, выполняющихся в одном потоке воркера для одного и того же запроса. Поскольку файберы PHP кооперативны, конкурентный доступ невозможен.

Сессия

php
$request->session(): ?SessionInterface

Возвращает представление $_SESSION только для чтения. Возвращает null, если session_start() не был вызван. Управление сессией (запуск, сохранение, уничтожение, запись значений) выполняется стандартными функциями сессий PHP.

php
<?php session_start(); $session = $request->session(); $userId = $session->get('user_id'); $isAdmin = $session->get('is_admin', false); // Write session data using standard PHP functions $_SESSION['last_seen'] = time();

SessionInterface

SessionInterface — это представление активной сессии только для чтения.

php
namespace OxPHP\Http; interface SessionInterface { public function id(): string; public function name(): string; public function get(string $key, mixed $default = null): mixed; public function has(string $key): bool; public function all(): array; }
Метод Описание
id() Идентификатор сессии
name() Имя сессии (по умолчанию: "PHPSESSID")
get(key, default) Одно значение из сессии или $default, если ключ отсутствует
has(key) true, если ключ существует в $_SESSION
all() Все данные сессии в виде массива

Значения сессии отражают текущее состояние $_SESSION на момент вызова, а не состояние на момент первого вызова session().

UploadedFileInterface

UploadedFileInterface представляет один загруженный файл.

php
namespace OxPHP\Http; interface UploadedFileInterface { public function name(): string; public function clientType(): string; public function type(): string; public function size(): int; public function tmpPath(): string; public function error(): int; public function isValid(): bool; public function moveTo(string $destination): bool; }
Метод Описание
name() Исходное имя файла, отправленное клиентом
clientType() MIME-тип, заявленный клиентом, — не доверяйте этому значению при принятии решений о безопасности
type() MIME-тип, определённый по фактическому содержимому файла с помощью анализа магических байтов. Возвращает "application/octet-stream", если тип не удаётся определить. Кэшируется при первом вызове.
size() Размер файла в байтах
tmpPath() Путь к временному файлу на диске
error() Одна из констант UPLOAD_ERR_*
isValid() true, когда error() равно UPLOAD_ERR_OK
moveTo(path) Перемещает файл в $path. Перед перемещением вызывает type(). Возвращает false, если файл некорректен или перемещение не удалось.

Всегда проверяйте isValid() перед использованием загруженного файла. Используйте type(), а не clientType(), при принятии решений, связанных с безопасностью:

php
<?php $file = $request->file('document'); if (!$file || !$file->isValid()) { http_response_code(400); echo json_encode(['error' => 'Upload failed or missing']); return; } $detectedMime = $file->type(); $allowed = ['application/pdf', 'image/jpeg', 'image/png']; if (!in_array($detectedMime, $allowed, true)) { http_response_code(415); echo json_encode(['error' => "File type not allowed: $detectedMime"]); return; } $file->moveTo('/var/uploads/' . bin2hex(random_bytes(8)) . '.pdf');

AttributesInterface

AttributesInterface — единственная изменяемая часть объекта запроса. Он предназначен для хранения метаданных уровня запроса — аутентифицированного пользователя, разрешённых параметров маршрута, локали, флагов функций, — к которым нужен доступ из разных частей приложения.

php
namespace OxPHP\Http; interface AttributesInterface { public function get(string $key, mixed $default = null): mixed; public function set(string $key, mixed $value): void; public function has(string $key): bool; public function remove(string $key): void; public function all(): array; }
Метод Описание
get(key, default) Возвращает значение для $key или $default, если отсутствует
set(key, value) Сохраняет значение
has(key) true, если ключ был установлен
remove(key) Удаляет ключ
all() Все атрибуты в виде ассоциативного массива

Исключения

Вызов oxphp_http_request() вне контекста активного запроса выбрасывает исключение из пространства имён OxPHP\Http\Exception.

php
namespace OxPHP\Http\Exception; class NoActiveRequestException extends \RuntimeException {} class AsyncContextException extends NoActiveRequestException {} class WorkerIdleException extends NoActiveRequestException {}
Исключение Когда выбрасывается
NoActiveRequestException Нет активного HTTP-запроса: CLI, MINIT, после завершения работы или файбер, переживший свой запрос
AsyncContextException Внутри колбэка oxphp_async() — асинхронные воркеры выполняются в отдельных потоках без контекста запроса
WorkerIdleException Режим воркеров, между запросами — воркер ожидает следующий запрос

AsyncContextException и WorkerIdleException наследуются от NoActiveRequestException, поэтому перехват базового класса покрывает все случаи.

php
<?php try { $request = oxphp_http_request(); } catch (\OxPHP\Http\Exception\AsyncContextException $e) { // Inside oxphp_async() — no request context here } catch (\OxPHP\Http\Exception\WorkerIdleException $e) { // Worker is between requests — do not call oxphp_http_request() here } catch (\OxPHP\Http\Exception\NoActiveRequestException $e) { // Any other case with no active request }

В обычном коде обработки запросов этот try/catch не нужен. Защита от исключений полезна в коде инициализации (bootstrap), который может выполняться вне контекста запроса.

SUPERGLOBALS_ENABLED

bash
SUPERGLOBALS_ENABLED=true # default — full backward compatibility SUPERGLOBALS_ENABLED=false # superglobals are empty arrays

По умолчанию OxPHP заполняет $_GET, $_POST, $_COOKIE, $_FILES и $_SERVER как обычно. В этом режиме объектный HTTP API доступен наряду с суперглобальными переменными.

Установка SUPERGLOBALS_ENABLED=false делает эти массивы пустыми, что устраняет затраты на их построение для каждого запроса. Перечисленное ниже продолжает работать независимо от этой настройки:

Возможность Поведение при SUPERGLOBALS_ENABLED=false
oxphp_http_request() Всегда доступен
php://input Доступен (это поток, а не суперглобальная переменная)
$_SESSION Доступен (управляется модулем сессий PHP)
header(), headers_list() Доступны (функции вывода SAPI)
session_start(), session_*() Доступны (нативные функции PHP)
$_GET, $_POST, $_COOKIE, $_FILES, $_SERVER Пустые массивы

Используйте oxphp_superglobals_enabled(), чтобы проверить текущую настройку во время выполнения:

php
<?php if (!oxphp_superglobals_enabled()) { $method = oxphp_http_request()->method(); } else { $method = $_SERVER['REQUEST_METHOD']; }

Режим воркеров

В режиме воркеров для каждого входящего запроса создаётся новый объект Request. Объект предыдущего запроса становится недействительным, как только запрос завершается. Не сохраняйте ссылку на него между запросами.

worker.php
<?php // worker.php require __DIR__ . '/vendor/autoload.php'; $app = new MyApp\Application(); oxphp_worker(function () use ($app) { $request = oxphp_http_request(); $app->handle($request); });

Все кэши объекта Request (разобранные заголовки, cookies, параметры строки запроса, тело запроса) очищаются автоматически при начале следующего запроса.

Поддержка в IDE

Установите пакет со стабами, чтобы получить автодополнение и проверку типов в PhpStorm, VS Code или любом редакторе с поддержкой LSP:

bash
composer require --dev oxphp/stubs

Пакет со стабами предоставляет:

text
oxphp-stubs/ ├── OxPHP/Http/ │ ├── RequestInterface.php │ ├── SessionInterface.php │ ├── UploadedFileInterface.php │ ├── AttributesInterface.php │ ├── Request.php │ ├── Session.php │ ├── UploadedFile.php │ ├── Attributes.php │ └── Exception/ │ ├── NoActiveRequestException.php │ ├── AsyncContextException.php │ └── WorkerIdleException.php └── functions.php

Никакой зависимости времени выполнения не добавляется. Пакет предназначен только для require-dev.

Примеры

Традиционный режим

php
<?php $request = oxphp_http_request(); $method = $request->method(); // "GET" $path = $request->path(); // "/api/articles" $page = $request->query('page', 1); // "2" or 1 (default) // Authorization header if (!$request->hasHeader('Authorization')) { http_response_code(401); echo json_encode(['error' => 'Unauthorized']); exit; } $token = $request->header('Authorization'); // Structured logging with request metadata error_log(sprintf( '[%s] %s %s from %s', oxphp_request_id(), $method, $path, $request->ip() ));

POST с JSON-телом

php
<?php $request = oxphp_http_request(); if (!$request->isMethod('POST')) { http_response_code(405); exit; } $email = $request->payload('email'); $password = $request->payload('password'); if (!$email || !$password) { http_response_code(400); echo json_encode(['error' => 'email and password are required']); exit; } // payload() handles JSON, form-urlencoded, and multipart // No manual json_decode() or $_POST check needed $user = authenticate($email, $password); header('Content-Type: application/json'); echo json_encode(['token' => $user->generateToken()]);

Атрибуты в middleware

php
<?php // auth-middleware.php function authenticate_request(\OxPHP\Http\RequestInterface $request): void { $token = $request->header('Authorization'); if (!$token) { http_response_code(401); exit; } $user = verify_token(str_replace('Bearer ', '', $token)); if (!$user) { http_response_code(403); exit; } $request->attributes()->set('user', $user); } // route-handler.php $request = oxphp_http_request(); authenticate_request($request); $user = $request->attributes()->get('user'); echo json_encode(['id' => $user->id, 'name' => $user->name]);

Режим воркеров с сессией

worker.php
<?php // worker.php require __DIR__ . '/vendor/autoload.php'; oxphp_worker(function () { $request = oxphp_http_request(); if ($request->path() === '/login' && $request->isMethod('POST')) { $username = $request->payload('username'); $password = $request->payload('password'); if (verify_credentials($username, $password)) { session_start(); $_SESSION['user'] = $username; $_SESSION['authenticated'] = true; header('Location: /dashboard'); } else { http_response_code(401); echo 'Invalid credentials'; } return; } if ($request->path() === '/dashboard') { session_start(); $session = $request->session(); if (!$session || !$session->get('authenticated')) { header('Location: /login'); return; } echo 'Welcome, ' . htmlspecialchars($session->get('user')); } });

Смотрите также