Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Streams Laravel Package

guzzlehttp/streams

A lightweight PHP library for working with streams, offering object-oriented wrappers, lazy and buffered streams, filtering, limiting, and utility helpers. Once used by Guzzle to power HTTP request and response bodies, it remains useful for composing and manipulating stream data.

View on GitHub
Deep Wiki
Context7

Getting Started

First: confirm you actually have this package. guzzlehttp/streams is archived and is not something you add to a new Laravel app. It ships only as a legacy transitive dependency — effectively guzzlehttp/guzzle: ~5.0, which Laravel 5.x-era code and a handful of old SDKs pinned. Check with:

composer show guzzlehttp/streams          # present at all?
composer why guzzlehttp/streams           # who is dragging it in
composer show guzzlehttp/guzzle | grep versions

If your app runs Guzzle 6/7 (everything Laravel 6+ uses, including Illuminate\Support\Facades\Http), the same abstractions live in guzzlehttp/psr7 — see the mapping table below and use that instead. Everything after this paragraph applies only to code that genuinely still resolves GuzzleHttp\Stream\*.

Where to look first. The whole API is three things:

  • GuzzleHttp\Stream\Stream — the resource wrapper; Stream::factory($resource) is the entry point for 90% of usage.
  • GuzzleHttp\Stream\StreamInterface — the contract (read, write, seek, tell, eof, getContents, getSize, isSeekable, detach, getMetadata, __toString).
  • GuzzleHttp\Stream\Utils — static helpers, chiefly Utils::copyToStream($src, $dst, $maxLen = -1) and Utils::tryFopen($uri, $mode).

First use case. You have a raw PHP resource from somewhere Laravel-ish — Storage::disk('s3')->readStream($path), fopen('php://temp','w+'), a proc_open pipe — and some downstream API wants a uniform "readable thing" with eof() and getContents() semantics. Wrap it:

use GuzzleHttp\Stream\Stream;

$resource = Storage::disk('s3')->readStream('exports/customers.csv');
$stream   = Stream::factory($resource);

while (! $stream->eof()) {
    $chunk = $stream->read(8192);
    // ...
}

That is the entire value proposition. If you are not doing exactly this, you probably don't need the package.

Implementation Patterns

Compose rather than buffer. The decorator streams are the reason the package exists at all. In a Laravel job or console command:

  • LimitStream — send only the first N bytes of a file to an API; wrap once, don't slice strings.
  • AppendStream — concatenate a header, a CSV body and a footer into one upload body without a temp file.
  • CachingStream — makes a non-seekable source (S3 body, HTTP response, pipe) rewindable by spooling to php://temp. This is the one you will reach for most, because retries and payload signing both require rewinding.
  • NoSeekStream — the inverse: wrap a stream you hand to untrusted code so it can't rewind and replay it.
  • PumpStream, BufferStream, LazyOpenStream, DroppingStream — lazily-populated data, in-memory buffers, open-on-first-read files, and "skip the first N bytes" respectively.
use GuzzleHttp\Stream\CachingStream;
use GuzzleHttp\Stream\LimitStream;
use GuzzleHttp\Stream\Stream;

$raw    = Storage::disk('s3')->readStream('reports/big.pdf');
$cached = new CachingStream(Stream::factory($raw));   // now seekable/rewindable
$cached->seek(0);                                      // safe before a retry
$head   = new LimitStream($cached, 1024, 0);           // first 1 KiB, for sniffing

Streaming out of Laravel, not through it. The failure mode is $stream->getContents() into an Eloquent accessor and OOM-ing the worker. The correct exit shape is Laravel's own streaming response, which never holds the body:

return response()->streamDownload(function () use ($stream) {
    $out = fopen('php://output', 'w');
    \GuzzleHttp\Stream\Utils::copyToStream($stream, \GuzzleHttp\Stream\Stream::factory($out));
    fclose($out);
    $stream->close();
}, 'export.csv', ['Content-Type' => 'text/csv']);

Integration seams to know. Laravel's filesystem layer gives you resources, not streams — readStream(), Storage::path(), php://temp all need Stream::factory() before the package is useful. Conversely, anything that wants a PSR-7 body (Http::withBody(), Response::setContent(), Symfony's StreamedResponse in newer versions) will not accept this package's StreamInterface; you must bridge via getContents() or getMetadata('uri').

Testing. In Storage::fake('local') tests, assert on the resource, not the stream: $this->assertIsResource($fake->readStream(...)). When you must compare stream contents, rewind() first — the pointer is part of the object's state and PHPUnit's assertion failures will silently leave it advanced.

Migration path (the real long-term pattern). If you touch GuzzleHttp\Stream\*, port it in the same PR — the classes are one-for-one:

Legacy (GuzzleHttp\Stream) Current (GuzzleHttp\Psr7)
Stream::factory($r) Utils::streamFor($r)
Utils::copyToStream() Utils::copyToStream()
LimitStream LimitStream
CachingStream CachingStream
AppendStream AppendStream
LazyOpenStream LazyOpenStream
BufferStream BufferStream
PumpStream PumpStream
NoSeekStream NoSeekStream
DroppingStream DroppingStream

Constructor signatures are near-identical; psr7 additionally returns PSR-7 StreamInterface, so every Laravel/Guzzle type hint starts working again.

Gotchas and Tips

  • The interface is not PSR-7. GuzzleHttp\Stream\StreamInterface is a different interface from Psr\Http\Message\StreamInterface, despite identical method names. Passing one where the other is type-hinted yields a fatal TypeError that reads like a version mismatch and isn't. Grep your use statements before debugging anything else.
  • Archived means no PHP 8 support promises. The package predates PHP 8.0 union types and 8.1 deprecations. It may emit deprecation notices on modern PHP, and it will never get a security fix. Treat "we still depend on this" as a backlog item, not a fact of life. Run composer audit and check whether an old guzzlehttp/guzzle is pinning you.
  • getContents() drains the stream and is not idempotent. Neither is __toString() (which internally rewinds if seekable and then drains). Calling either twice gives you '' the second time. Always rewind() between reads, and only on streams where isSeekable() is true.
  • getSize() returns null for unknown-length streams — pipes, chunked HTTP bodies, php://input. Any code doing intdiv($stream->getSize(), $chunk) will fatal on null. Fall back to a loop over eof() and never trust the size for allocation.
  • Non-seekable sources break retries and signing invisibly. S3/Guzzle 5 signature calculation hashes the payload; if the body was consumed by a first attempt, the retry fails with a signature error, not a stream error. Wrap in CachingStream before you enter the retry path.
  • detach() transfers ownership; close() afterwards does nothing. If you detach() and never fclose() the resource, you leak file handles in long-running queue workers. Prefer close() and let the wrapper clean up.
  • Composer lockfiles can contain both packages, giving you two similarly-named Stream classes and a use statement that resolves to whichever one you didn't want. When a Stream method mysteriously doesn't exist, get_class($stream) is the fastest diagnostic.
  • Extension point: Stream::factory() accepts an options array (size, seekable, readable, writable, and a metadata array). If you have a resource wrapping an unusual URI scheme, overriding size avoids the fstat() round-trip and the null-size trap above — but you own the correctness of that number.
  • Utils::tryFopen() swallows the fopen warning and throws a RuntimeException with the underlying message. It is the safer call in a web request path, where a PHP warning becomes a 500 with a leaked filesystem path.
  • Don't put a stream in a queued job payload. Serialising a Stream writes the metadata array and loses the resource; the worker gets a closed or empty stream. Pass the path or the storage key and re-open inside handle().
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
3mdikram/worldmoneywords
snowsoft/config
itmosoft/laravel-subscriptions
larva/laravel-ai-qwen
diephp/sequences
users-au/socialite-provider
twidpay/twid-logger
mrnewport/laravel-plaid
mrnewport/laravel-repo
mrnewport/laravel-docsign
mrnewport/laravel-stow
mrnewport/laravel-flow
mrnewport/laravel-priceable
mrnewport/laravel-nestedterms
fredmeng/laravel-apidoc-generator
itstructure/laravel-multilingual-tools
pdustdar/elasticsearch
markupai/php-sdk
rlacerda83/lumem-microservice-shipment
uni-method/symfony-lib