API HTTP-запиту
OxPHP надає обʼєктно-орієнтований API для доступу до даних HTTP-запиту. Замість того щоб читати $_GET, $_POST, $_COOKIE, $_FILES і $_SERVER, ви викликаєте методи обʼєкта Request, який повертає саме те, що ви запитали, не більше й не менше.
Зміст
- Огляд
- Отримання екземпляра Request
- Методи RequestInterface
- SessionInterface
- UploadedFileInterface
- AttributesInterface
- Винятки
- SUPERGLOBALS_ENABLED
- Режим воркерів
- Підтримка в IDE
- Приклади
Огляд
oxphp_http_request() повертає проксі лише для читання до даних HTTP-запиту, що зберігаються в поточному потоці воркера. Дані завантажуються ліниво. Окремий виклик методу, як-от $request->header('Accept'), звертається безпосередньо до структури даних на боці Rust і повертає лише це значення. Серед викликів, що повертають увесь масив, query() і payload() зберігають розібраний результат в обʼєкті, на якому їх викликали; headers(), cookies() і files() щоразу будують свій результат заново. Для запиту нічого не зберігається: кожен виклик oxphp_http_request() повертає новий обʼєкт із новим набором цих кешів.
Чому варто використовувати його замість суперглобальних змінних?
- Розбір JSON-тіла вбудовано.
$request->payload()без додаткового коду повертає декодоване тілоapplication/jsonза будь-якого методу запиту, а також поля тіла формиPOST(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
$request = oxphp_http_request();Викликайте oxphp_http_request() будь-де в скрипті, що виконується в межах активного HTTP-запиту, зокрема всередині колбека oxphp_worker():
<?php
oxphp_worker(function () {
$request = oxphp_http_request();
$method = $request->method();
// ...
});Методи RequestInterface
URI і метод
$request->method(): stringПовертає HTTP-метод у верхньому регістрі: "GET", "POST", "PUT", "PATCH", "DELETE" тощо.
$request->isMethod(string $method): boolПеревірка методу без урахування регістру.
$request->path(): stringШлях URI без рядка запиту: "/users/42".
$request->fullUri(): stringПовний URI зі схемою, хостом, необовʼязковим нестандартним портом, шляхом і рядком запиту: "https://example.com:8080/users/42?page=2". Стандартні порти (80 для HTTP, 443 для HTTPS) пропускаються.
$request->scheme(): string"https" або "http".
$request->isSecure(): booltrue, коли схема — "https".
$request->host(): stringІмʼя хоста із заголовка Host. Повертає порожній рядок, коли заголовка немає (запити HTTP/1.0 без заголовка Host).
$request->port(): intПорт із заголовка Host. Якщо його не вказано явно, повертає типовий порт для схеми: 80 для HTTP, 443 для HTTPS.
scheme(), isSecure(), host() і port() враховують X-Forwarded-Proto та X-Forwarded-Host, коли TRUSTED_PROXIES містить співрозмовника зʼєднання. Без довірених проксі вони відображають пряме зʼєднання.
$request->queryString(): ?stringСирий рядок запиту без початкового ?. Повертає null, коли рядка запиту немає.
Протокол
$request->httpProtocol(): stringПовний рядок протоколу: "HTTP/1.1" або "HTTP/2".
$request->httpProtocolVersion(): stringЛише номер версії: "1.1" або "2".
Параметри рядка запиту
$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 наразі дає плаский запис з імʼям tags[] замість масиву tags, а повторюване імʼя через query('name') повертає перше входження, тоді як query() зберігає останнє. Доки це не виправлено, читайте параметри-масиви через $_GET.
// Request: GET /search?q=oxphp&tags[]=php&tags[]=async
$q = $request->query('q'); // "oxphp"
$tags = $_GET['tags']; // ["php", "async"]Знайдені значення завжди є рядками. Якщо ключа немає, $default повертається без змін. Параметр, надісланий без значення (?flag), і параметр, надісланий порожнім (?flag=), обидва читаються як '' — null дає лише відсутній параметр.
Декодування
Імена та значення проходять percent-декодування, а + читається як пробіл — за тими самими правилами, що їх PHP застосовує, будуючи $_GET:
// Request: GET /search?q=%D0%9F%D1%80%D0%B8%D0%B2%D0%B5%D1%82&tag=a+b
$request->query('q'); // "Привет"
$request->query('tag'); // "a b"Значення є байтовими рядками, тож escape-послідовність, що кодує невалідний UTF-8, зберігається без змін — підписаний токен чи двійковий ідентифікатор усе одно можна перевірити за байтами в тому вигляді, в якому їх надіслано. Прочитати рядок запиту точно таким, яким він надійшов, без декодування, як і раніше можна через queryString().
Імена повертаються так, як їх надіслав клієнт, тоді як $_GET повертає їх у тому вигляді, в якому їх записав PHP. PHP замінює ' ', '.' і '[' в іменах суперглобальних змінних, тому ?a.b=1 потрапляє в $_GET як a_b:
// Request: GET /?a.b=1
$_GET['a_b']; // "1"
$request->query('a.b'); // "1"Імʼя також зберігається повністю там, де PHP його обрізав би. PHP обробляє імена суперглобальних змінних як C-рядки, тому ?a%00b=1 стає $_GET['a'], тоді як query() зберігає a\0b. Не сприймайте це як привід перевіряти дані через один API, а читати через інший: обрізання дає змогу одному параметру потрапити на ключ іншого, тож ?a=1&a%00x=2 дає $_GET['a'] === '2', тоді як query('a') === '1', і ці два способи розходяться у значенні, а не лише в наборі імен.
Розбирається щонайбільше 1000 параметрів. PHP застосовує власне обмеження, max_input_vars, яке типово також дорівнює 1000, але його можна змінити — підвищте його, і $_GET міститиме параметри після того місця, де query() зупиняється.
Розібране тіло запиту
$request->payload(?string $key = null, mixed $default = null): mixedПовертає розібране тіло запиту. Тіло розбирається відповідно до заголовка Content-Type:
| Content-Type | Повертає |
|---|---|
application/x-www-form-urlencoded |
Асоціативний масив значень полів для POST-запиту; порожній масив для будь-якого іншого методу |
multipart/form-data |
Асоціативний масив значень текстових полів для POST-запиту; порожній масив для будь-якого іншого методу |
application/json |
Декодоване значення: масив для обʼєкта чи масиву, інакше сам скаляр (string, int, float, bool); null для невалідного JSON і для тіла, що складається з літерала null |
| Будь-яке інше значення | null. Зіставлення відбувається за префіксом, тож усе, що починається з application/json (наприклад, application/json-patch+json), обробляється за рядком JSON вище, а text/json і application/vnd.api+json потрапляють сюди |
JSON-тіло декодується за будь-якого методу запиту. Тіло форми — ні: обидва рядки для форм читають те, на що тіло розібрав PHP, а PHP робить це лише для POST, тому payload() для PUT чи PATCH з полями форми повертає порожній масив. Надсилайте такі тіла як JSON або читайте їх через body() і розбирайте самостійно.
Для обох типів форм payload() повертає поля в тому вигляді, в якому PHP розібрав їх із тіла. Подальший запис у $_POST змінює лише копію масиву в скрипті, і payload() цього не бачить.
Розібраний результат кешується під час першого виклику й повторно використовується наступними викликами на тому самому обʼєкті. Кожен виклик oxphp_http_request() повертає окремий обʼєкт із власним кешем, тож другий обʼєкт будує результат заново.
| Виклик | Повертає |
|---|---|
$request->payload() |
Усе розібране тіло |
$request->payload(null, []) |
Усе розібране тіло або [], коли його немає: порожнє тіло, Content-Type з останнього рядка таблиці вище, невалідний JSON або літерал JSON null |
$request->payload('email') |
Значення одного поля або null, якщо його немає |
$request->payload('email', '') |
Значення одного поля або '', якщо його немає |
Пошук за ключем працює лише для payload-масиву. У JSON-тілі, яке декодувалося у скаляр, ключів немає, тому payload('anything') повертає значення за замовчуванням. Тіло, що декодується у false чи 0, повертається саме цим значенням; значення за замовчуванням його не замінює.
<?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"]Заголовки
$request->header(string $name, ?string $default = null): ?stringПовертає сире значення заголовка. Імена заголовків нечутливі до регістру. Для заголовків із кількома значеннями (Accept, X-Forwarded-For) увесь рядок заголовка повертається одним рядком. Розбирати його — ваше завдання.
$request->hasHeader(string $name): boolПовертає true, якщо заголовок із таким імʼям присутній.
$request->headers(): arrayПовертає всі заголовки як асоціативний масив. Кожен ключ — це імʼя заголовка в отриманому вигляді (без нормалізації), а кожне значення — сирий рядок заголовка.
<?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
$request->cookie(string $name, ?string $default = null): ?stringПовертає значення одного cookie або $default, якщо такого cookie немає. Cookie, надісланий порожнім (Cookie: a=), читається як ''; $default дає лише відсутній cookie.
$request->cookies(): arrayПовертає всі cookies як асоціативний масив пар «імʼя — значення».
Декодування
Cookies декодуються інакше, ніж параметри рядка запиту, — так само, як це робить PHP, будуючи $_COOKIE: значення проходять percent-декодування, але + залишається буквальним +, а імена не декодуються взагалі.
// Cookie: token=a%20b; sep=a+b; %D0%BA=1
$request->cookie('token'); // "a b" — percent-escapes decoded
$request->cookie('sep'); // "a+b" — unlike query(), "+" is not a space
$request->cookie('%D0%BA'); // "1" — look up by the name as sentТут імена так само повертаються в надісланому вигляді. PHP замінює ' ', '.' і '[' в іменах усіх суперглобальних змінних, зокрема й cookies, тому Cookie: a.b=1 потрапляє в $_COOKIE як a_b, тоді як cookie('a.b') і cookies()['a.b'] використовують імʼя, надіслане клієнтом.
Значення є байтовими рядками, тож підписаний cookie чи setcookie('sid', random_bytes(16)) повертається рівно тими байтами, з якими його встановили.
<?php
$theme = $request->cookie('theme', 'light'); // "dark" or "light"
$session = $request->cookie('session'); // null if absent
$all = $request->cookies(); // ["theme" => "dark", ...]Сире тіло запиту
$request->body(): stringПовертає сирі байти тіла запиту. Це відповідник file_get_contents('php://input') в OxPHP. На відміну від payload(), body() не кешується. Кожен виклик звертається до базової структури даних.
$request->contentType(): ?stringПовертає значення заголовка Content-Type або null, якщо його немає.
<?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() незалежні. Можна викликати обидва в тому самому запиті.
Завантаження файлів
$request->file(string $name): ?UploadedFileInterfaceПовертає завантажений файл для вказаного імені поля або null, якщо такого поля немає. Для полів-масивів (name="photos[]") повертає перший файл.
$request->files(?string $name = null): array| Виклик | Повертає |
|---|---|
$request->files() |
Усі завантажені файли як плаский масив UploadedFileInterface |
$request->files('photos') |
Усі файли поля photos (підтримує name="photos[]") |
Обидва щоразу будують нові обʼєкти UploadedFileInterface. Два виклики file('avatar') повертають два обʼєкти для того самого завантаженого файлу, і тип, який один із них визначив через type(), іншому не відомий. Якщо обʼєкт потрібен більше одного разу, зберігайте той, що ви отримали, як це робить приклад нижче.
<?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()));
}
}Клієнт
$request->ip(): stringПовертає IP-адресу клієнта. Коли налаштовано TRUSTED_PROXIES і співрозмовник запиту входить до довіреного набору, це крайня права недовірена адреса з X-Forwarded-For або з Forwarded за RFC 7239. Коли TRUSTED_PROXIES містить cloudflare і ланцюжок переспрямування показує, що запит надійшов з edge-вузла Cloudflare, це натомість адреса з CF-Connecting-IP. Інакше це IP прямого співрозмовника — зазвичай вашого балансувальника навантаження, а не кінцевого клієнта.
Сирий заголовок X-Forwarded-For і далі доступний через $request->header('X-Forwarded-For') для складних випадків, але розібрати його вручну правильно вдається рідко (крайня ліва чи крайня права адреса, жодної перевірки довіри за CIDR). Натомість налаштуйте TRUSTED_PROXIES. Див. Довірені проксі.
Час запиту
$request->startTime(bool $asFloat = false): int|floatПовертає мітку часу Unix моменту, коли цей запит було отримано.
| Виклик | Повертає |
|---|---|
$request->startTime() |
Ціле число секунд: 1711234567 |
$request->startTime(true) |
Число з рухомою комою з точністю до долей секунди: 1711234567.3412 |
<?php
$elapsed = microtime(true) - $request->startTime(true);
error_log(sprintf("Request took %.3fs so far", $elapsed));Атрибути
$request->attributes(): AttributesInterfaceПовертає змінюваний контейнер атрибутів, що належить цьому обʼєкту Request. Використовуйте атрибути, щоб передавати дані від middleware до обробників маршрутів і до будь-якого іншого коду, що отримує той самий обʼєкт, замість глобальної змінної.
<?php
// In authentication middleware
$request->attributes()->set('user', $authenticatedUser);
// In the route handler
$user = $request->attributes()->get('user');oxphp_http_request() під час кожного виклику повертає новий Request, і контейнер, який ви з нього берете, теж новий і порожній. Запис, зроблений через один виклик, не можна прочитати через інший: get() натомість повертає значення за замовчуванням, і жодного попередження чи запису в журналі немає.
<?php
oxphp_http_request()->attributes()->set('tenant_id', $id);
oxphp_http_request()->attributes()->get('tenant_id'); // null — a second Request, a second containerЯкщо ви передаєте обʼєкт далі, як це робить приклад вище з $request, увесь код, що його тримає, читає й записує той самий контейнер, зокрема й Fiber, якому ви його передали. Fiber, який сам викликає oxphp_http_request(), отримує власний контейнер, як і будь-який інший викликач. Оскільки Fiber у PHP кооперативні, жодні два з них не звертаються до контейнера одночасно.
Скидати щось між запитами не потрібно. Контейнер звільняється разом з обʼєктом, якому він належить, а обробник наступного запиту викликає oxphp_http_request() і отримує власний.
Сесія
$request->session(): ?SessionInterfaceПовертає представлення $_SESSION лише для читання. Повертає null, якщо session_start() не викликали. Керування сесією (запуск, збереження, знищення, запис значень) виконується стандартними функціями сесій 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 — це представлення активної сесії лише для читання.
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 представляє один завантажений файл.
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-тип, визначений за фактичним вмістом файлу за магічними байтами (mime_content_type() з ext/fileinfo). Повертає "application/octet-stream", коли тип визначити неможливо. Визначається під час першого виклику й зберігається в цьому обʼєкті (див. Визначення типу). |
size() |
Розмір файлу в байтах |
tmpPath() |
Шлях до тимчасового файлу на диску |
error() |
Одна з констант UPLOAD_ERR_* |
isValid() |
true, коли error() дорівнює UPLOAD_ERR_OK |
moveTo(path) |
Переміщує файл до $path через move_uploaded_file(), не визначаючи його типу. Повертає false, якщо файл невалідний або переміщення не вдалося. Що читає type() після переміщення, описано в розділі Визначення типу. |
Завжди перевіряйте isValid(), перш ніж використовувати завантажений файл. У рішеннях, чутливих до безпеки, використовуйте type(), а не clientType():
<?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');Визначення типу
Перший виклик type() читає файл і зберігає результат в обʼєкті; наступні виклики на цьому обʼєкті повертають його, не читаючи файл знову. Це стосується й "application/octet-stream" після невдалого визначення, зокрема коли перший виклик кинув виняток під обробником помилок, що перетворює попередження на винятки: наступні виклики повертають це значення без попередження. Обʼєкт з іншого виклику file() чи files() читає файл знову під час власного першого виклику.
Без mime_content_type() (ext/fileinfo не завантажено або функцію вказано в disable_functions) кожен виклик, який мав би читати файл, видає попередження й повертає "application/octet-stream", не зберігаючи його, або кидає виняток під обробником помилок, що перетворює попередження на винятки. OxPHP також записує про це один рядок на процес на рівні warn — у лог сервера або туди, куди вказує error_log, якщо його задано. Самі попередження журналюються, як і будь-яке інше попередження PHP.
moveTo() не визначає тип. Якщо type() не викликали на обʼєкті до переміщення, його перший виклик після успішного переміщення читає файл у місці призначення:
- Локальний шлях — це переміщений файл, який знаходять за абсолютним шляхом, куди його поклало переміщення. Пізніша зміна робочого каталогу не має значення, включно з тією, яку PHP робить сам перед виконанням shutdown-функцій і деструкторів.
- URL обгортки потоку відкривається знову через свою обгортку, і тип визначається з того, що вона повертає. Якщо обгортка не може відкрити URL для читання (з попередженням) або не може виконати для нього
stat(як-отcompress.zlib://; у PHP 8.4 — з попередженням),type()повертає"application/octet-stream"або кидає виняток під обробником помилок, що перетворює попередження на винятки. - URL
data:ніколи не декодується.move_uploaded_file()перейменовує файл на нього як на локальне імʼя файлу, яке потім читаєtype(), або, якщо це не вдається, копіює файл до URL, який не приймає записів: проходить лише порожнє завантаження, нічого не записується, іtype()не знаходить файлу. - Інший обʼєкт для того самого завантаження, на якому
type()не викликали до переміщення (наприклад, отриманий знову черезfile()), не знаходить файлу.mime_content_type()видає попередження про відсутній файл, іtype()цього обʼєкта повертає"application/octet-stream"або кидає виняток під обробником помилок, що перетворює попередження на винятки.
Викликайте type() перед moveTo(), коли місцем призначення є обгортка потоку або коли переміщений файл можуть перемістити далі, видалити чи перезаписати, перш ніж ви запитаєте його тип. Обʼєкт зберігає те, що визначив цей виклик.
AttributesInterface
AttributesInterface — єдина змінювана частина обʼєкта запиту. Він призначений для метаданих, які виводить ваш власний код (автентифікований користувач, визначені параметри маршруту, локаль, прапорці функцій) і потім знову читає далі тим самим ланцюжком викликів. Ці дані бачить лише код, який отримав обʼєкт Request, що містить контейнер; див. Атрибути.
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.
namespace OxPHP\Http\Exception;
class NoActiveRequestException extends \RuntimeException {}
class AsyncContextException extends NoActiveRequestException {}
class WorkerIdleException extends NoActiveRequestException {}| Виняток | Коли кидається |
|---|---|
NoActiveRequestException |
Немає активного HTTP-запиту: CLI, MINIT, після завершення роботи або у Fiber, що пережив свій запит |
AsyncContextException |
Усередині колбека oxphp_async() — асинхронні воркери працюють в окремих потоках без контексту запиту |
WorkerIdleException |
Режим воркерів, проміжок між запитами — воркер очікує на наступний запит |
AsyncContextException і WorkerIdleException обидва успадковують NoActiveRequestException, тому перехоплення базового класу охоплює всі випадки.
<?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
SUPERGLOBALS_ENABLED=true # default — full backward compatibility
SUPERGLOBALS_ENABLED=false # the $_SERVER request keys and $_GET are not builtТипово OxPHP заповнює $_GET, $_POST, $_COOKIE, $_FILES і $_SERVER як зазвичай. У цьому режимі HTTP Object API доступний поряд із суперглобальними змінними.
Встановлення SUPERGLOBALS_ENABLED=false пропускає роботу з опису запиту для PHP: змінні CGI та HTTP_* не реєструються, а рядок запиту не передається PHP на розбір. Ефект вужчий, ніж підказує назва. Тіло запиту й заголовок Cookie потрапляють до PHP у будь-якому разі, і PHP будує похідні від них масиви незалежно від цього налаштування:
| Можливість | Поведінка з SUPERGLOBALS_ENABLED=false |
|---|---|
oxphp_http_request() |
Доступна; відповідає кожен метод, зокрема query() і payload() |
php://input |
Доступний (це потік, а не суперглобальна змінна) |
$_SESSION |
Доступна (нею керує модуль сесій PHP) |
header(), headers_list() |
Доступні (функції виводу SAPI) |
session_start(), session_*() |
Доступні (нативні функції PHP) |
$_POST, $_FILES, $_COOKIE |
Заповнюються як зазвичай. Вони будуються з тіла й заголовка Cookie, яких це налаштування не торкається |
$_REQUEST |
Заповнюється. PHP обʼєднує його як завжди (див. Суперглобальні змінні); бракує лише того, що додав би $_GET |
$_GET |
Порожня |
$_SERVER |
Під час запиту — лише чотири ключі: REQUEST_TIME, REQUEST_TIME_FLOAT, argc і argv, які реєструє сам PHP, а не сервер. REQUEST_METHOD, REQUEST_URI, ключі HTTP_* і оточення процесу відсутні. У режимі воркерів початкове завантаження відбувається інакше: код над oxphp_worker() виконується до будь-якого запиту й бачить $_SERVER, побудований так, ніби налаштування увімкнене, разом з усім оточенням процесу, а також REQUEST_URI-заглушку / |
Отже, це налаштування не прибирає дані запиту з глобальних змінних PHP і не заважає розбору тіла форми. Воно пропускає лише заповнення $_SERVER для кожного запиту (змінні CGI та HTTP_*, а також оточення процесу) і розбір рядка запиту в $_GET силами PHP.
Код, що маршрутизує за $_SERVER['REQUEST_URI'], за цього налаштування нічого там не знаходить і має читати oxphp_http_request()->path(). У вхідному скрипті воркера це стосується обробника. Код початкового завантаження над ним бачить описану в таблиці заглушку /, яка теж не є справжнім маршрутом.
Перевірити поточне значення налаштування під час виконання можна за допомогою oxphp_superglobals_enabled():
<?php
if (!oxphp_superglobals_enabled()) {
$method = oxphp_http_request()->method();
} else {
$method = $_SERVER['REQUEST_METHOD'];
}Режим воркерів
У режимі воркерів oxphp_http_request() будує новий Request під час кожного виклику, а не один раз на запит. Викликайте його всередині обробника й передавайте обʼєкт туди, де він потрібен, як це робить приклад нижче.
<?php
// worker.php
require __DIR__ . '/vendor/autoload.php';
$app = new MyApp\Application();
oxphp_worker(function () use ($app) {
$request = oxphp_http_request();
$app->handle($request);
});Не зберігайте обʼєкт довше, ніж живе обробник, який його побудував. Обʼєкт, утримуваний між запитами, не стає недійсним і ніколи не повідомляє, що застарів. method(), path(), header() та інші некешовані методи читання повертають дані того запиту, який потік воркера обслуговує в момент виклику. Кешовані значення (масив query(), розібраний payload(), контейнер атрибутів) містять те, що кожен метод захопив під час першого виклику; метод, уперше викликаний під час поточного запиту, захоплює дані цього запиту. Під час початку наступного запиту в Request нічого не очищується. Що саме охоплює мʼяке скидання між запитами, перелічено в розділі Що скидається, а що зберігається.
Підтримка в IDE
Установіть пакет заглушок, щоб отримати автодоповнення й перевірку типів у PhpStorm, VS Code чи будь-якому редакторі з підтримкою LSP:
composer require --dev oxphp/stubsПакет заглушок містить:
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
$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
$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
// 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]);Режим воркерів із сесією
<?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;
session_write_close();
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')) {
session_write_close();
header('Location: /login');
return;
}
echo 'Welcome, ' . htmlspecialchars($session->get('user'));
session_write_close();
}
});Виклик session_write_close() на кожному шляху виходу переносить запис, а також звільнення всього, що заблокував обробник збереження, у ту точку, яку обирає ваш застосунок. Сесія, залишена відкритою, все одно записується й закривається, коли її запит завершується, — за умови, що в ній немає жодного іншого запиту, який несе воркер. Сесія, в якій перебуває інший запит, лишається відкритою, а обробник збереження й далі утримує все, що заблокував, доки воркер її не поверне. Запит перебуває в сесії, якщо він її відкрив або був прийнятий, поки вона була відкрита, — незалежно від того, чи викликає він колись session_start(). Тож довготривалий потік, прийнятий у цей проміжок, тримає сесію відкритою весь час, поки він працює, а запити, прийняті тим часом, отримують її навіть після того, як запит, що її відкрив, повернувся.
Ні власноруч виконане закриття, ні закриття наприкінці запиту не розділяють запити, що перекриваються. Воркер приймає нові запити щоразу, коли запит, який він виконує, призупиняється, і сесія, досі відкрита в точці призупинення, дістається тому запиту, який було прийнято в цей проміжок. Сесія, закрита через session_write_close() до цієї точки, лишає встановленим свій ідентифікатор, і запит, прийнятий у цей проміжок, підхоплює його власним session_start(). Див. Режим воркерів.
Промпти для вашого агента
Ці промпти розраховані на агента з підключеним MCP-сервером документації OxPHP — потрібні сторінки він прочитає сам.
Переведи мій код із суперглобальних змінних на API запиту OxPHP. Прочитай сторінку `php/request-api` через MCP-сервер документації OxPHP.
Знайди в цьому репозиторії місця, що читають дані запиту напряму, і покажи переписаний варіант для тих із них, де це змінює поведінку, а не лише стиль.Дивіться також
- Суперглобальні змінні — як OxPHP заповнює
$_SERVER,$_GET,$_POST,$_COOKIEі$_FILES - Функції PHP — повний довідник з
oxphp_http_request(),oxphp_superglobals_enabled()та всіх інших вбудованих функцій - Режим воркерів — постійні воркери PHP і життєвий цикл запиту
- Довідник конфігурації —
SUPERGLOBALS_ENABLEDта інші змінні оточення