guzzlehttp/psr7
PSR-7 HTTP message implementation for PHP: request, response, URI, uploaded file, and stream objects compatible with Guzzle and any PSR-7 library. Create or inspect messages without sending requests; includes factories and helpers.
Installation:
composer require guzzlehttp/psr7
Ensure your composer.json specifies "guzzlehttp/psr7": "^3.2" (3.2.0 contains important stream-integrity fixes — see Gotchas).
First Use Case: Create a basic HTTP request/response using PSR-7 interfaces:
use GuzzleHttp\Psr7\Request;
use GuzzleHttp\Psr7\Response;
// Create a request
$request = new Request('GET', 'https://api.example.com/data');
// Create a response (simulated)
$response = new Response(200, [], '{"status": "success"}');
// Use with a client (e.g., Guzzle HTTP client)
$client = new \GuzzleHttp\Client();
$result = $client->send($request);
Key Classes to Know:
Request, Response: Core HTTP message implementations.StreamInterface: PSR-7 stream abstraction (e.g., AppendStream, BufferStream).Utils: Helper methods for common tasks (e.g., streamFor(), copyToString()).InflateStream / AppendStream: Decoding and concatenation decorators with tightened error semantics in 3.2.0.Where to Look First:
GuzzleHttp\Psr7\Utils for utility functions.AppendStream, LimitStream) for advanced use cases.InflateStream and AppendStream if you already depend on either.$request = new Request('POST', '/upload', [
'Content-Type' => 'application/json',
], json_encode(['file' => 'data.bin']));
Utils::modifyRequest() to avoid cloning:
$modified = Utils::modifyRequest($request, [
'set_headers' => ['Authorization' => 'Bearer token123'],
'body' => Utils::streamFor('new-data'),
]);
$fileStream = Utils::streamFor(fopen('large_file.bin', 'r'));
$appendStream = new AppendStream([$fileStream, Utils::streamFor('extra-data')]);
LimitStream to split large files:
$original = Utils::streamFor(fopen('huge_file.bin', 'r'));
$chunk = new LimitStream($original, 5 * 1024 * 1024, 0); // 5MB chunks
AppendStream times out, bytes already delivered from the current source are preserved. Resume from the offset already consumed instead of restarting the whole append chain:
$append = new AppendStream([$partA, $partB]);
$buffer = '';
while (!$append->eof()) {
$chunk = $append->read(8192); // a timeout throw no longer loses earlier bytes
$buffer .= $chunk;
}
$query = Query::parse('foo=bar&baz[]=1&baz[]=2');
// ['foo' => 'bar', 'baz' => ['1', '2']]
$built = Query::build(['foo' => 'bar', 'baz' => [1, 2]]);
// 'foo=bar&baz=1&baz=2'
StreamDecoratorTrait:
class LoggingStream implements StreamInterface {
use StreamDecoratorTrait;
public function read($length) {
$data = $this->stream->read($length);
logger()->debug("Read {$length} bytes: " . substr($data, 0, 20));
return $data;
}
}
$decoded = new InflateStream($response->getBody());
$json = Utils::copyToString($decoded); // throws on a source read failure in 3.2+
$promise = $client->sendAsync($request);
$promise->then(function (Response $response) {
$body = Utils::copyToString($response->getBody());
});
Leverage Utils for Common Tasks:
Utils::streamFor($resource).Utils::copyToStream($source, $dest).Handle Large Data:
BufferStream to control memory usage:
$buffered = new BufferStream(1024); // 1KB buffer
Debugging Streams:
CachingStream for non-seekable streams (e.g., HTTP redirects):
$stream = new CachingStream($nonSeekableStream);
Multipart Uploads:
$multipart = new MultipartStream([
['name' => 'file', 'contents' => $fileStream],
['name' => 'description', 'contents' => 'Uploaded via API'],
]);
Testing:
FnStream for unit tests:
$mockStream = FnStream::decorate($stream, [
'read' => function () { return 'mocked-data'; },
]);
Wrap Decoders in Error Handling (3.2.0):
InflateStream now surfaces source read failures that occur after decoded bytes were already produced. Wrap decompression in try/catch and treat a partial body as fatal rather than silently truncated:
try {
$body = Utils::copyToString(new InflateStream($encoded));
} catch (\RuntimeException $e) {
report($e); // do not persist the partially decoded payload
}
Pin ^3.2 in Long-Running Workers:
AppendStream timeout fix; older 3.x releases can silently drop bytes mid-chain.Stream Ownership:
fclose()) does not close the stream. Use StreamInterface::close() explicitly.$stream->close() when done.Seekable vs. Non-Seekable Streams:
PumpStream, FnStream) may not support seeking. Check isSeekable() before calling seek().CachingStream to wrap non-seekable streams if seeking is required.Memory Leaks:
BufferStream and CachingStream buffer data in memory. Monitor memory usage for large streams.BufferStream(1024)).Query String Parsing:
Query::parse() does not handle nested arrays (e.g., foo[a]=1&foo[b]=2 becomes ['foo[a]' => '1'], not ['foo' => ['a' => '1']]).Stream Decorator Order:
$stream = new LimitStream(new LoggingStream($originalStream));
// LoggingStream is inner; LimitStream is outer.
UTF-8 Headers:
mb_convert_encoding() if needed.Deprecated Methods:
Header::normalize() is deprecated. Use Header::splitList() instead.InflateStream Now Fails Loudly (3.2.0):
Utils::copyToString(new InflateStream($s)) returns a complete body — catch exceptions and validate checksums/lengths before use. Also check getMetadata('uncompressed_size') where available.AppendStream Timeout Behaviour Changed (3.2.0):
$stream->tell() on seekable parts) and resume from those offsets rather than restarting the whole AppendStream.Upgrade Order for Nested Decorators:
InflateStream and AppendStream are in the chain, upgrade and re-test together: the two fixes interact (partial append + decoding failure).Inspect Stream Contents:
Utils::copyToString($stream) to dump stream contents (be cautious with large streams).Check Stream Metadata:
$stream->getMetadata() to debug issues (e.g., buffer sizes, seekability).Enable Guzzle Debugging:
$client->getConfig(['debug' => fopen('debug.log', 'w')]);
Validate PSR-7 Compliance:
Reproduce Truncation Reports:
composer show guzzlehttp/psr7; anything below 3.2.0 can exhibit the InflateStream silent-truncation and AppendStream byte-loss behaviour.Custom Stream Decorators:
StreamDecoratorTrait for reusable stream logic (e.g., compression, encryption). Ensure your read() propagates failures instead of returning '', matching the 3.2.0 decoder semantics.Stream Wrappers:
StreamWrapper::getResource() to integrate with PHP's stream functions (e.g., fopen(), fread()).Message Parsing:
Message::parseRequest() or Message::parseResponse() for custom message formats.Query String Customization:
Query::build() or Query::parse() for domain-specific query handling.Event-Driven Streams:
PumpStream with callbacks for event-driven processing:
$pumpStream = new PumpStream(function ($length) {
return $generator->current() ?: false;
});
Resilient Source Streams:
StreamInterface so that AppendStream and InflateStream never see a raw timeout — they now treat such failures as hard errors rather than truncation points.How can I help you explore Laravel packages today?