Skip to content

Latest commit

 

History

History
387 lines (265 loc) · 10.7 KB

File metadata and controls

387 lines (265 loc) · 10.7 KB

Streams and Decorators

PSR-7 request and response bodies are streams. This page covers stream creation, cursor and I/O behavior, built-in stream decorators, and wrapping PSR-7 streams as PHP resources.

Streams allow HTTP messages to represent small strings, large files, generated data, remote resources, and other body sources through a common interface.

The PSR-7 Psr\Http\Message\StreamInterface exposes methods that let consumers read, write, seek, and inspect body data without requiring the entire body to be loaded into memory.

Streams expose their capabilities using isReadable(), isWritable(), and isSeekable(). These methods help collaborators determine whether a stream supports the operations they need.

Creating Streams

Use GuzzleHttp\Psr7\Utils::streamFor() to create streams from common PHP values. It accepts strings, resources returned from fopen(), objects that implement __toString(), iterators, callable arrays, closures, invokable objects, and existing Psr\Http\Message\StreamInterface instances.

Strings and null are stored in php://temp streams. PHP keeps php://temp data in memory until the stream exceeds 2 MB, then spills to a temporary file on disk. Non-string scalars such as integers, floats, and booleans are rejected; cast them to strings first.

Callable sources receive a suggested read length, may return fewer or more bytes, and end the stream by returning false or null. Strings remain literal body contents, even when they name a callable.

use GuzzleHttp\Psr7\Utils;

$stream = Utils::streamFor('string data');
echo $stream;
// string data
echo $stream->read(3);
// str
echo $stream->getContents();
// ing data
var_export($stream->eof());
// true
var_export($stream->tell());
// 11

You can create streams from iterators. The iterator can yield any number of bytes per iteration. Any excess bytes returned by the iterator that were not requested by a stream consumer will be buffered until a subsequent read.

use GuzzleHttp\Psr7\Utils;

$generator = function ($bytes) {
    for ($i = 0; $i < $bytes; $i++) {
        yield '.';
    }
};

$stream = Utils::streamFor($generator(1024));
echo $stream->read(3);
// ...

Metadata

Streams expose stream metadata through getMetadata(). This method provides the data returned by PHP's stream_get_meta_data(), and can optionally expose custom metadata.

use GuzzleHttp\Psr7\Utils;

$resource = Utils::tryFopen('/path/to/file', 'r');
$stream = Utils::streamFor($resource);

echo $stream->getMetadata('uri');
// /path/to/file
var_export($stream->isReadable());
// true
var_export($stream->isWritable());
// false
var_export($stream->isSeekable());
// true

AppendStream

GuzzleHttp\Psr7\AppendStream

Reads from multiple streams, one after the other.

use GuzzleHttp\Psr7;

$a = Psr7\Utils::streamFor('abc, ');
$b = Psr7\Utils::streamFor('123.');
$composed = new Psr7\AppendStream([$a, $b]);

$composed->addStream(Psr7\Utils::streamFor(' Above all listen to me'));

echo $composed; // abc, 123. Above all listen to me.

BufferStream

GuzzleHttp\Psr7\BufferStream

Provides a buffer stream that can be written to fill a buffer, then read from it to remove bytes from the buffer.

This stream returns a "hwm" metadata value that tells upstream consumers what the configured high water mark of the stream is, or the maximum preferred size of the buffer.

use GuzzleHttp\Psr7;

// When the buffer reaches or exceeds 1024 bytes, it will begin returning 0 to
// writes. This is an indication that writers should slow down.
$buffer = new Psr7\BufferStream(1024);

CachingStream

The CachingStream is used to allow seeking over previously read bytes on non-seekable streams. This can be useful when transferring a non-seekable entity body fails due to needing to rewind the stream (for example, resulting from a redirect). Data that is read from the remote stream will be buffered in a PHP temp stream so that previously read bytes are cached first in memory, then on disk.

use GuzzleHttp\Psr7;

$original = Psr7\Utils::streamFor(fopen('http://www.google.com', 'r'));
$stream = new Psr7\CachingStream($original);

$stream->read(1024);
echo $stream->tell();
// 1024

$stream->seek(0);
echo $stream->tell();
// 0

By default the bytes are cached in a php://temp stream. You can supply your own cache target as the second constructor argument, but it is used as a random-access byte buffer to replay the remote stream, so it must be readable, writable, and seekable, report an accurate position and size, and store writes losslessly. Lossy or non-seekable streams such as BufferStream and DroppingStream are not valid targets.

DroppingStream

GuzzleHttp\Psr7\DroppingStream

Stream decorator that begins dropping data once the size of the underlying stream becomes too full.

use GuzzleHttp\Psr7;

// Create an empty stream
$stream = Psr7\Utils::streamFor();

// Start dropping data when the stream has more than 10 bytes
$dropping = new Psr7\DroppingStream($stream, 10);

$dropping->write('01234567890123456789');
echo $stream; // 0123456789

FnStream

GuzzleHttp\Psr7\FnStream

Compose stream implementations based on a hash of callables.

Allows for easy testing and extension of a provided stream without needing to create a concrete class for a simple extension point.

use GuzzleHttp\Psr7;

$stream = Psr7\Utils::streamFor('hi');
$fnStream = Psr7\FnStream::decorate($stream, [
    'rewind' => function () use ($stream) {
        echo 'About to rewind - ';
        $stream->rewind();
        echo 'rewound!';
    }
]);

$fnStream->rewind();
// Outputs: About to rewind - rewound!

InflateStream

GuzzleHttp\Psr7\InflateStream

Uses PHP's zlib.inflate filter to inflate zlib (HTTP deflate, RFC1950) or gzipped (RFC1952) content.

This stream decorator converts the provided stream to a PHP stream resource, appends the zlib.inflate filter, and wraps the filtered resource as a stream.

Closing an InflateStream also closes the compressed source stream it decorates; detach() leaves the source stream open.

LazyOpenStream

GuzzleHttp\Psr7\LazyOpenStream

Lazily reads from or writes to a file that is opened only after an I/O operation takes place on the stream.

use GuzzleHttp\Psr7;

$stream = new Psr7\LazyOpenStream('/path/to/file', 'r');
// The file has not yet been opened...

echo $stream->read(10);
// The file is opened and read from only when needed.

LimitStream

GuzzleHttp\Psr7\LimitStream

LimitStream can be used to read a subset or slice of an existing stream object. This can be useful for breaking a large file into smaller pieces to be sent in chunks (e.g. Amazon S3's multipart upload API).

use GuzzleHttp\Psr7;

$original = Psr7\Utils::streamFor(fopen('/tmp/test.txt', 'r+'));
echo $original->getSize();
// >>> 1048576

// Limit the size of the body to 1024 bytes and start reading from byte 2048
$stream = new Psr7\LimitStream($original, 1024, 2048);
echo $stream->getSize();
// >>> 1024
echo $stream->tell();
// >>> 0

MultipartStream

GuzzleHttp\Psr7\MultipartStream

A stream that returns bytes for a streaming multipart or multipart/form-data body when read.

Each multipart element must contain a name and contents key. contents may be any non-array value accepted by GuzzleHttp\Psr7\Utils::streamFor(), including closures and invokable objects. Array contents are recursively expanded into nested form fields.

NoSeekStream

GuzzleHttp\Psr7\NoSeekStream

NoSeekStream wraps a stream and does not allow seeking.

use GuzzleHttp\Psr7;

$original = Psr7\Utils::streamFor('foo');
$noSeek = new Psr7\NoSeekStream($original);

echo $noSeek->read(3);
// foo
var_export($noSeek->isSeekable());
// false

try {
    $noSeek->seek(0);
} catch (\RuntimeException $e) {
    echo $e->getMessage();
    // Cannot seek a NoSeekStream
}

PumpStream

GuzzleHttp\Psr7\PumpStream

Provides a read-only stream that pumps data from a PHP callable.

When invoking the provided callable, the PumpStream will pass the suggested number of bytes to read to the callable. The callable can choose to ignore this value and return fewer or more bytes than requested. Any extra data returned by the provided callable is buffered internally until drained using the read() method of the PumpStream. The provided callable MUST return a non-empty string to provide data, and MUST return false or null when there is no more data to read. Returning an empty string causes a RuntimeException because it cannot satisfy a positive-length read.

Userland callables that declare no parameters are tolerated by PHP, but length-aware callables remain the recommended formal shape.

Implementing Stream Decorators

Creating a stream decorator is very easy thanks to the GuzzleHttp\Psr7\StreamDecoratorTrait. This trait provides methods that implement Psr\Http\Message\StreamInterface by proxying to an underlying stream. Just use the StreamDecoratorTrait and implement your custom methods.

For example, let's say we wanted to call a specific function each time the last byte is read from a stream. This could be implemented by overriding the read() method.

use Psr\Http\Message\StreamInterface;
use GuzzleHttp\Psr7\StreamDecoratorTrait;

class EofCallbackStream implements StreamInterface
{
    use StreamDecoratorTrait;

    private $callback;

    private $stream;

    public function __construct(StreamInterface $stream, callable $cb)
    {
        $this->stream = $stream;
        $this->callback = $cb;
    }

    public function read(int $length): string
    {
        $result = $this->stream->read($length);

        // Invoke the callback when EOF is hit.
        if ($this->eof()) {
            ($this->callback)();
        }

        return $result;
    }
}

This decorator could be added to any existing stream and used like so:

use GuzzleHttp\Psr7;

$original = Psr7\Utils::streamFor('foo');

$eofStream = new EofCallbackStream($original, function () {
    echo 'EOF!';
});

$eofStream->read(2);
$eofStream->read(1);
// echoes "EOF!"
$eofStream->seek(0);
$eofStream->read(3);
// echoes "EOF!"

PHP StreamWrapper

You can use the GuzzleHttp\Psr7\StreamWrapper class if you need to use a PSR-7 stream as a PHP stream resource.

Use the GuzzleHttp\Psr7\StreamWrapper::getResource() method to create a PHP stream from a PSR-7 stream.

use GuzzleHttp\Psr7\StreamWrapper;

$stream = GuzzleHttp\Psr7\Utils::streamFor('hello!');
$resource = StreamWrapper::getResource($stream);
echo fread($resource, 6); // outputs hello!

Related