Request
Lift\Http\Request — це незмінний об’єкт HTTP-запиту. Він реалізує Psr\Http\Message\ServerRequestInterface (тому сумісний із будь-яким коли-небудь написаним PSR-15 middleware) і додає поверх дружніші скорочення.
Ментальна модель:
Request— це знімок одного вхідного HTTP-виклику. Він незмінний. Будь-який метод, який «змінює» його (with*), повертає новий об’єкт — оригінал залишається недоторканим. Це зроблено навмисно і є тим самим правилом, якого дотримується будь-яка бібліотека PSR-7.
Як отримати запит
У продакшені ви майже ніколи не створюєте Request самі. Просто вкажіть його тип в обробнику, і Lift його впровадить:
use Lift\Http\Request;
$app->get('/users/{id}', function (Request $req) {
return ['id' => $req->param('id')];
});
Інші способи — корисні для тестів або нестандартних точок входу:
// Із суперглобалей PHP ($_SERVER, $_GET, $_POST, $_FILES, $_COOKIE, php://input)
$req = Request::fromGlobals();
// Вручну (чудово для тестів)
use Lift\Http\Uri;
$req = new Request('GET', new Uri('http://localhost/users/1'));
Читання вхідних даних
| Джерело | Метод |
|---|---|
Параметр маршруту /users/{id} |
$req->param('id') |
Рядок запиту ?page=2 |
$req->query('page') |
| Поле тіла форми/JSON | $req->input('name') |
| Усе тіло JSON | $req->json() (повертає масив) |
| Усе розібране тіло | $req->getParsedBody() (PSR-7) |
| Cookie | $req->cookie('session') |
| Завантажений файл | $req->file('avatar') |
| Значення заголовка | $req->getHeaderLine('Accept') |
Метод (GET, POST, …) |
$req->getMethod() |
| Повний об’єкт URI | $req->getUri() |
| Лише шлях | $req->getUri()->getPath() |
| Атрибут middleware | $req->getAttribute('user') |
| Усі серверні змінні | $req->getServerParams() (≈ $_SERVER) |
Параметри маршруту
// Маршрут: /users/{id}
$id = $req->param('id'); // '42' (завжди рядок)
$id = (int) $req->param('id'); // 42 (приводьте тип самі)
$id = $req->param('missing', 0); // запасне значення за замовчуванням
$all = $req->params(); // ['id' => '42']
// Нижчого рівня: сирий асоціативний масив усіх збіглих параметрів маршруту
$raw = $req->getRouteParams(); // ['id' => '42']
// Корисно в тестах — побудувати запит із конкретними параметрами маршруту
$req = $req->withRouteParams(['id' => '42', 'slug' => 'hello']);
Рядок запиту
// URL: /search?q=lift&page=2
$q = $req->query('q'); // 'lift'
$page = (int) $req->query('page', 1); // 2
$all = $req->getQueryParams(); // ['q' => 'lift', 'page' => '2']
Тіло запиту
Для запитів POST, PUT і PATCH Lift розбирає тіло автоматично на основі Content-Type:
application/json→ розбирається у масив, доступний через$req->json()і$req->input(...).- Усе інше → використовується
$_POST(тобтоapplication/x-www-form-urlencodedіmultipart/form-data).
// Form POST: name=Alice&[email protected]
$name = $req->input('name');
// JSON POST: {"name":"Alice","email":"[email protected]"}
$name = $req->input('name'); // працює так само
$email = $req->json()['email']; // і прямий доступ до масиву теж
Щоб прочитати сире тіло (наприклад, підписи вебхуків, яким потрібні нерозібрані байти):
$raw = (string) $req->getBody();
Якщо тіло потрібне кілька разів або після того, як його прочитав middleware, перемотайте потік:
$req->getBody()->rewind();.
Завантажені файли
$avatar = $req->file('avatar'); // ?Psr\Http\Message\UploadedFileInterface
if ($avatar !== null && $avatar->getError() === UPLOAD_ERR_OK) {
$avatar->moveTo(__DIR__ . '/../storage/uploads/' . $avatar->getClientFilename());
}
Доступна інформація з об’єкта файлу:
$avatar->getSize(); // байти
$avatar->getClientFilename(); // 'me.png'
$avatar->getClientMediaType(); // 'image/png'
$avatar->getError(); // UPLOAD_ERR_OK тощо
$avatar->getStream(); // потік PSR-7
Кілька файлів під одним полем (<input type="file" name="docs[]" multiple>):
// $req->getUploadedFiles() повертає нормалізоване дерево
foreach ($req->getUploadedFiles()['docs'] ?? [] as $file) {
/* ... */
}
Cookie
$session = $req->cookie('session');
$all = $req->getCookieParams(); // ['session' => '...', 'lang' => 'en']
Щоб встановити cookie, див. Cookie у Response.
Заголовки
$accept = $req->getHeaderLine('Accept'); // 'application/json'
$lines = $req->getHeader('Accept'); // ['application/json'] (форма списку)
$has = $req->hasHeader('Authorization'); // bool
$all = $req->getHeaders(); // ['Accept' => [...], ...]
Імена заголовків регістронезалежні ('Accept' і 'accept' обидва працюють).
Помічники / скорочення
$req->isJson(); // Content-Type містить application/json
$req->wantsJson(); // Accept містить application/json
$req->isMethod('POST'); // перевірка методу (регістронезалежна)
$req->getMethod(); // 'GET' | 'POST' | …
$req->getUri()->getPath(); // '/users/42'
Типове їх використання:
$app->get('/users/{id}', function (Request $req) use ($repo) {
$user = $repo->find((int) $req->param('id'));
if ($req->wantsJson()) {
return Response::json($user);
}
return Response::html($view->render('users.show', ['user' => $user]));
});
Валідація — спосіб в один рядок
Request::validate() об’єднує параметри запиту + тіло + параметри маршруту, проганяє їх через Валідатор і або повертає валідовані дані, або викидає ValidationException (який типовий обробник помилок Lift перетворює на HTTP 422):
$app->post('/users', function (Request $req) use ($repo) {
$data = $req->validate([
'name' => 'required|string|min:2|max:255',
'email' => 'required|email|unique:users,email',
'age' => 'integer|min:13',
]);
return Response::json($repo->create($data), 201);
});
Власні повідомлення про помилки:
$data = $req->validate(
['email' => 'required|email'],
['email.required' => 'Нам потрібен ваш email, щоб надіслати посилання.'],
);
Передайте Translator для локалізованих повідомлень:
$data = $req->validate($rules, [], $translator);
Див. Валідацію для повного переліку правил.
Атрибути middleware — передавання даних униз конвеєром
Middleware може прикріплювати довільні значення до запиту, а обробник їх читає. Загальноприйнятий носій для «автентифікованого користувача», «claims із JWT», «ідентифікатора запиту» тощо.
// У middleware:
$req = $req->withAttribute('user', $authenticatedUser);
return $handler->handle($req);
// В обробнику:
$user = $req->getAttribute('user'); // null, якщо не встановлено
$user = $req->getAttribute('user', $default); // зі значенням за замовчуванням
$all = $req->getAttributes();
Атрибути — на кожен запит, ніколи не розділяються і зникають, коли запит завершується. Вони не є шиною подій і не персистентні — для цього використовуйте Події або Сесії.
Незмінність PSR-7
Найчастіша помилка на початку роботи з PSR-7:
// ❌ НЕПРАВИЛЬНО — нічого не робить. with*() повертає НОВИЙ об’єкт.
$req->withHeader('X-Foo', 'bar');
$req->withAttribute('user', $user);
// ✅ ПРАВИЛЬНО — захопіть новий екземпляр.
$req = $req->withHeader('X-Foo', 'bar');
$req = $req->withAttribute('user', $user);
Якщо ви викликаєте $req->withFoo(...) й ігноруєте повернене значення, нічого не змінюється, бо нижчележний об’єкт незмінний. Це правило застосовне до кожного методу PSR-7, не лише до методів Lift.
Плавний ланцюжок працює чудово, бо кожен виклик повертає новий екземпляр:
$req = $req
->withHeader('X-Trace', $traceId)
->withAttribute('user', $user)
->withAttribute('start', microtime(true));
Менш поширені методи PSR-7, які можуть знадобитися
$req->getProtocolVersion(); // '1.1', '2.0'
$req->getRequestTarget(); // '/users/42?page=1'
$req->withMethod('POST');
$req->withUri($newUri); // повертає клон із новим URI
$req->withQueryParams(['x' => 1]); // замінити query
$req->withParsedBody($newBody); // замінити тіло
$req->withoutAttribute('user');
Часті підводні камені
| Симптом | Причина | Виправлення |
|---|---|---|
$req->json() повертає [] для тіла JSON |
Невірний/відсутній заголовок Content-Type: application/json |
Змусьте клієнт надсилати правильний заголовок. |
$req->input('name') дорівнює null, але поле є в URL |
input() читає лише тіло — використовуйте query('name') |
Використовуйте правильний читач. |
Виклик withFoo(...) «не працює» |
Ви не присвоїли результат назад | $req = $req->withFoo(...). |
$req->file('avatar') дорівнює null |
У формі відсутній enctype="multipart/form-data" |
Додайте enctype. |
$req->param('id') дорівнює '42', а не 42 |
Усі параметри маршруту — рядки | Приведіть тип: (int) $req->param('id'). |
| Читання тіла двічі дає порожнечу | Потік input PHP неперемотуваний у деяких SAPI |
Або закешуйте $raw = (string) $req->getBody(); один раз і повторно використовуйте, або використовуйте $req->json() Lift (він перечитує внутрішньо). |
Шпаргалка
// Ввід
$req->param('id'); // маршрут
$req->query('page', 1); // рядок запиту
$req->input('name'); // поле тіла (форма або JSON)
$req->json(); // усе тіло JSON як масив
$req->file('avatar'); // ?UploadedFileInterface
$req->cookie('session');
// Огляд
$req->getMethod();
$req->getUri()->getPath();
$req->getHeaderLine('Authorization');
$req->isJson() / wantsJson() / isMethod('POST');
// Валідація
$data = $req->validate(['email' => 'required|email']);
// Middleware → обробник
$req = $req->withAttribute('user', $user);
$user = $req->getAttribute('user');
// PSR-7 (завжди присвоюйте результат!)
$req = $req->withMethod('POST')->withHeader('X-Foo', 'bar');