Request
Lift\Http\Request is an immutable HTTP-request object. It implements Psr\Http\Message\ServerRequestInterface (so it's compatible with every PSR-15 middleware ever written), and adds friendlier shortcuts on top.
Mental model: a
Requestis a snapshot of one incoming HTTP call. It's immutable. Any method that "changes" it (with*) returns a new object — the original is untouched. This is by design and is the same rule every PSR-7 library follows.
Getting hold of the request
You almost never construct a Request yourself in production. Just type-hint it in your handler and Lift will inject it:
use Lift\Http\Request;
$app->get('/users/{id}', function (Request $req) {
return ['id' => $req->param('id')];
});
Other ways — useful for tests or non-standard entry points:
// From PHP superglobals ($_SERVER, $_GET, $_POST, $_FILES, $_COOKIE, php://input)
$req = Request::fromGlobals();
// Manually (great for tests)
use Lift\Http\Uri;
$req = new Request('GET', new Uri('http://localhost/users/1'));
Reading input
| Source | Method |
|---|---|
Route parameter /users/{id} |
$req->param('id') |
Query string ?page=2 |
$req->query('page') |
| Form/JSON body field | $req->input('name') |
| Whole JSON body | $req->json() (returns array) |
| Whole parsed body | $req->getParsedBody() (PSR-7) |
| Cookie | $req->cookie('session') |
| Uploaded file | $req->file('avatar') |
| Header value | $req->getHeaderLine('Accept') |
Method (GET, POST, …) |
$req->getMethod() |
| Full URI object | $req->getUri() |
| Path only | $req->getUri()->getPath() |
| Middleware attribute | $req->getAttribute('user') |
| All server vars | $req->getServerParams() (≈ $_SERVER) |
Route parameters
// Route: /users/{id}
$id = $req->param('id'); // '42' (always string)
$id = (int) $req->param('id'); // 42 (cast it yourself)
$id = $req->param('missing', 0); // fallback default
$all = $req->params(); // ['id' => '42']
// Lower-level: raw associative array of all matched route params
$raw = $req->getRouteParams(); // ['id' => '42']
// Useful in tests — build a request with specific route params
$req = $req->withRouteParams(['id' => '42', 'slug' => 'hello']);
Query string
// 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']
Request body
For POST, PUT, and PATCH requests Lift parses the body automatically based on Content-Type:
application/json→ parsed into an array, available via$req->json()and$req->input(...).- Anything else →
$_POSTis used (i.e.application/x-www-form-urlencodedandmultipart/form-data).
// Form POST: name=Alice&[email protected]
$name = $req->input('name');
// JSON POST: {"name":"Alice","email":"[email protected]"}
$name = $req->input('name'); // works the same
$email = $req->json()['email']; // direct array access too
To read the raw body (e.g. webhook signatures that need the unparsed bytes):
$raw = (string) $req->getBody();
If you need the body multiple times or after middleware has read it, rewind the stream:
$req->getBody()->rewind();.
Uploaded files
$avatar = $req->file('avatar'); // ?Psr\Http\Message\UploadedFileInterface
if ($avatar !== null && $avatar->getError() === UPLOAD_ERR_OK) {
$avatar->moveTo(__DIR__ . '/../storage/uploads/' . $avatar->getClientFilename());
}
Available info from the file object:
$avatar->getSize(); // bytes
$avatar->getClientFilename(); // 'me.png'
$avatar->getClientMediaType(); // 'image/png'
$avatar->getError(); // UPLOAD_ERR_OK etc.
$avatar->getStream(); // PSR-7 stream
Multiple files under one field (<input type="file" name="docs[]" multiple>):
// $req->getUploadedFiles() returns the normalised tree
foreach ($req->getUploadedFiles()['docs'] ?? [] as $file) {
/* ... */
}
Cookies
$session = $req->cookie('session');
$all = $req->getCookieParams(); // ['session' => '...', 'lang' => 'en']
To set a cookie, see Response cookies.
Headers
$accept = $req->getHeaderLine('Accept'); // 'application/json'
$lines = $req->getHeader('Accept'); // ['application/json'] (list form)
$has = $req->hasHeader('Authorization'); // bool
$all = $req->getHeaders(); // ['Accept' => [...], ...]
Header names are case-insensitive ('Accept' and 'accept' both work).
Helpers / shortcuts
$req->isJson(); // Content-Type contains application/json
$req->wantsJson(); // Accept contains application/json
$req->isMethod('POST'); // method check (case-insensitive)
$req->getMethod(); // 'GET' | 'POST' | …
$req->getUri()->getPath(); // '/users/42'
A typical usage of these:
$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]));
});
Validation, the one-liner way
Request::validate() merges query params + body + route params, runs them through the Validator, and either returns the validated data or throws ValidationException (which Lift's default error handler turns into 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);
});
Custom error messages:
$data = $req->validate(
['email' => 'required|email'],
['email.required' => 'We need your email to send the link.'],
);
Pass a Translator for localized messages:
$data = $req->validate($rules, [], $translator);
See Validation for the full rule list.
Middleware attributes — passing data downstream
Middleware can attach arbitrary values to the request, and the handler reads them. The conventional carrier for "the authenticated user", "the JWT claims", "the request ID", etc.
// In middleware:
$req = $req->withAttribute('user', $authenticatedUser);
return $handler->handle($req);
// In the handler:
$user = $req->getAttribute('user'); // null if not set
$user = $req->getAttribute('user', $default); // with default
$all = $req->getAttributes();
Attributes are per-request, never shared, and disappear when the request finishes. They are not an event bus and not persistent — for those use Events or Sessions.
PSR-7 immutability
The single most-common mistake when starting with PSR-7:
// ❌ WRONG — does nothing. with*() returns a NEW object.
$req->withHeader('X-Foo', 'bar');
$req->withAttribute('user', $user);
// ✅ RIGHT — capture the new instance.
$req = $req->withHeader('X-Foo', 'bar');
$req = $req->withAttribute('user', $user);
If you call $req->withFoo(...) and ignore the return value, nothing changes because the underlying object is immutable. This rule applies to every PSR-7 method, not just Lift's.
Fluent chaining works fine because each call returns the new instance:
$req = $req
->withHeader('X-Trace', $traceId)
->withAttribute('user', $user)
->withAttribute('start', microtime(true));
Less-common PSR-7 methods you might need
$req->getProtocolVersion(); // '1.1', '2.0'
$req->getRequestTarget(); // '/users/42?page=1'
$req->withMethod('POST');
$req->withUri($newUri); // returns clone with new URI
$req->withQueryParams(['x' => 1]); // replace query
$req->withParsedBody($newBody); // replace body
$req->withoutAttribute('user');
Common pitfalls
| Symptom | Cause | Fix |
|---|---|---|
$req->json() returns [] for a JSON body |
Wrong/missing Content-Type: application/json header |
Make the client send the right header. |
$req->input('name') is null but the field is in the URL |
input() only reads body — use query('name') instead |
Use the right reader. |
Calling withFoo(...) "doesn't work" |
You didn't assign the result back | $req = $req->withFoo(...). |
$req->file('avatar') is null |
The form is missing enctype="multipart/form-data" |
Add the enctype. |
$req->param('id') is '42' not 42 |
All route params are strings | Cast: (int) $req->param('id'). |
| Reading body twice yields empty | The PHP input stream is not rewindable in some SAPIs |
Either cache $raw = (string) $req->getBody(); once and reuse, or use Lift's $req->json() (it re-reads internally). |
Cheat sheet
// Input
$req->param('id'); // route
$req->query('page', 1); // query string
$req->input('name'); // body field (form or JSON)
$req->json(); // entire JSON body as array
$req->file('avatar'); // ?UploadedFileInterface
$req->cookie('session');
// Inspect
$req->getMethod();
$req->getUri()->getPath();
$req->getHeaderLine('Authorization');
$req->isJson() / wantsJson() / isMethod('POST');
// Validate
$data = $req->validate(['email' => 'required|email']);
// Middleware → handler
$req = $req->withAttribute('user', $user);
$user = $req->getAttribute('user');
// PSR-7 (always assign result!)
$req = $req->withMethod('POST')->withHeader('X-Foo', 'bar');