psr/http-message
PSR-7 HTTP message interfaces for PHP (Request/Response, Streams, URIs, UploadedFiles). Defines common contracts only—no concrete implementation. Ideal for framework-agnostic middleware and libraries needing interoperable HTTP message types.
psr/http-message is not a standalone implementation—it’s a set of PSR-7 interfaces defining how HTTP messages (requests, responses, streams, URIs, etc.) should behave. To use it day-to-day:
guzzlehttp/psr7, nyholm/psr7, or laminas/laminas-diactoros (e.g., composer require guzzlehttp/psr7).ServerRequestInterface, ResponseInterface, etc.—not implementations.ServerRequestInterface (incoming request), ResponseInterface (outgoing response), and MessageInterface (shared methods).PSR7-Interfaces.md for quick method lookup—especially headers, body, URI, and attribute methods.First real use case: Build a middleware that inspects request headers and returns a 400 if a required header is missing:
use Psr\Http\Message\ServerRequestInterface as Request;
use Psr\Http\Message\ResponseInterface as Response;
function validateApiKey(Request $request, callable $next): Response
{
if (!$request->hasHeader('X-API-Key')) {
return (new Response())->withStatus(400, 'Missing API key');
}
return $next($request);
}
Immutable message construction: PSR-7 objects are immutable. Always use with*() methods to return new instances:
$response = $response->withStatus(201);
$response = $response->withHeader('Location', '/users/123');
(Don’t mutate in place.)
Body handling best practices:
getBody() once and reuse the reference:
$body = $response->getBody();
$body->write(json_encode(['success' => true]));
$body->rewind();
$stream = $response->getBody()->detach();
$response = $response->withBody(new Stream(fopen('/large.csv', 'r')));
URI manipulation: Build or modify URIs with UriInterface—avoid string concatenation:
$uri = $request->getUri()->withPath('/api/v2/users');
$newRequest = $request->withUri($uri);
Attribute-driven workflows: Use ServerRequestInterface::getAttribute()/withAttribute() to pass data through middleware (e.g., authenticated user, route params):
// In auth middleware
$request = $request->withAttribute('user', $user);
// In controller
$user = $request->getAttribute('user');
Uploaded files: Normalize handling via UploadedFileInterface—never access $_FILES directly:
$uploadedFiles = $request->getUploadedFiles();
$avatar = $uploadedFiles['avatar'] ?? null;
if ($avatar && $avatar->getError() === UPLOAD_ERR_OK) {
$avatar->moveTo('/uploads/' . $avatar->getClientFilename());
}
Middleware interop: The interface is foundational for PSR-15/PSR-15-like middleware stacks (e.g., Slim, Zend Expressive, custom stacks).
Immutable ≠ intuitive: It’s easy to forget to reassign the result of with*() calls:
// ❌ No effect: response unchanged
$response->withStatus(201);
// ✅ Correct
$response = $response->withStatus(201);
Headers are arrays internally: getHeader() returns an array (e.g., ['text/html']), getHeaderLine() returns a comma-separated string ('text/html'). Don’t assume single items.
Body streams are read/write by default: Many implementations use memory streams—write then rewind() before reading. getContents() starts at the current position.
getParsedBody() vs getParsedBody() && withParsedBody(): Often empty until parsed by middleware (e.g., slim/psr7 auto-parses JSON/URL-encoded bodies; others require guzzlehttp/psr7’s RequestFactory or middleware).
No built-in implementation: This package only provides interfaces. You must choose and trust an implementation. Check compatibility (e.g., nyholm/psr7 is lightweight and HHVM-compatible; guzzlehttp/psr7 is battle-tested but heavier).
Debugging tip: Use (string) $uri, (string) $body, or var_dump(get_class($message)) to inspect concrete types and contents quickly.
Extension points: Implement your own StreamInterface for custom storage (e.g., encrypting streams, S3-backed streams) or extend ServerRequestInterface implementations to add domain-specific methods (e.g., getJwtClaims()).
How can I help you explore Laravel packages today?