httpsoft/http-message
PSR-7 HTTP message implementation for PHP with strict types and clean, immutable value objects. Provides Request/Response, ServerRequest, URI, headers, streams, and factories, designed for interoperability across frameworks and middleware.
Install via Composer:
composer require httpsoft/http-message
This package provides pure PSR-7 implementations, so first tasks are straightforward:
use HttpSoft\Message\{Request, Response, ServerRequest, Stream, URI};ServerRequest for typical web middleware handling (e.g., inspecting incoming requests):$serverRequest = new ServerRequest($_SERVER, $_GET, $_POST, $_COOKIE, $_FILES);
$requestUri = $serverRequest->getUri(); // Returns a `URI` instance
$method = $serverRequest->getMethod();
$response = (new Response())
->withStatus(200)
->withHeader('Content-Type', 'text/plain')
->withBody(new Stream('php://memory', 'r+'))
->getBody()->write('Hello world');
Use the README and src as your primary reference—no heavy docs needed.
with*() method returns a new object. Chain safely:$request = $request
->withHeader('X-Request-ID', $id)
->withQueryParams([...$params, 'page' => $page]);
php://temp or fopen() streams:$body = new Stream(fopen('/tmp/data.csv', 'r'));
$response = (new Response())->withBody($body);
$serverRequest = $requestHandler->handle(new ServerRequest($_SERVER, $_GET));
// Then: return $response->withHeader('X-Cache', 'HIT');
$uri = (new URI('https://example.com/api'))
->withPath('/users')
->withQuery('limit=10');
$req = $req->with...) is the #1 mistake. Linters like PHPStan with immutable rules help.Stream instance across multiple messages is allowed per PSR-7—but ensure rewind() if reused for reading. php://memory is stateful.Content-Type use case-insensitive keys internally, but getHeaderLine('content-type') and getHeaderLine('Content-Type') both work. Stick to canonical casing (Content-Type) for clarity.Content-Length manually if streaming (e.g., files) unless your HTTP server/client handles chunked encoding. Stream::getSize() may return null for non-seekable streams—don’t rely blindly.withPath(rawurlencode($path)) or rely on withPath() + Uri::getPath()’s decoding.Stream for custom behavior (e.g., throttled reads), but avoid overriding immutable with*() methods—just delegate or wrap.instanceof Psr\Http\Message\RequestInterface is enough to swap implementations.How can I help you explore Laravel packages today?