Skip to content

Stream

composer require quillstack/stream

The simple implementation of PSR-7: Stream.

A request has a body, and so does a response. Sometimes that body is a file on disk, sometimes it is whatever was sent to the process, and very often there is none at all. Three streams rather than one, so the empty case costs nothing and the reading cases are honest about what they can and cannot do.

Requirements

  • PHP 8.1 or newer

Installation

shell
composer require quillstack/stream

Usage

The body of a request

InputStream reads php://input, which is what a request arrives in:

php
use Quillstack\Stream\InputStream;

$stream = new InputStream();
$body = (string) $stream;

A file

php
use Quillstack\Stream\FileStream;

$stream = new FileStream('/var/www/files/report.pdf');

$stream->getSize();      // bytes
$stream->read(1024);
$stream->rewind();
$stream->getContents();

The handle is closed when the stream is, and when it goes out of scope.

A body being sent

php
use Quillstack\Stream\TextStream;

$stream = new TextStream(json_encode(['hello' => 'world']));

InputStream does the same thing, but it is named for where a request arrives from and a body being sent somewhere is not that.

No body at all

php
use Quillstack\Stream\EmptyStream;

$stream = new EmptyStream();

(string) $stream;        // ''
$stream->getSize();      // 0
$stream->eof();          // true

Nothing is opened, so a response which has no body pays nothing for having one.

Reading

read($length) takes what was asked for and leaves the rest; getContents() is what is left of the stream from where the reader has got to; __toString() is always the whole thing:

php
$stream = new TextStream('abcdef');

$stream->read(3);        // 'abc'
$stream->tell();         // 3
$stream->getContents();  // 'def'
$stream->eof();          // true
(string) $stream;        // 'abcdef', wherever the reader is

Reaching for a body twice is what __toString() is for — reading the contents twice reads them once, which is what PSR-7 means by it.

A stream over a string is seekable, because a string held in memory is.

Technical documentation

All three implement Psr\Http\Message\StreamInterface, so anything taking a PSR-7 stream takes any of them.

ClassWhat it reads
InputStreamphp://input — the body of the request being handled, or a string given to it
TextStreama string somebody already has, on its way out
FileStreama file on disk, opened when the stream is built
EmptyStreamnothing; every read is empty and every size is zero

Asking a stream for something it cannot do throws rather than answering wrongly:

ExceptionThrown when
StreamNotReadableExceptionreading a stream which is not readable
StreamNotWritableExceptionwriting to a stream which is not writable
StreamNotSeekableExceptionseeking a stream which cannot seek

All three extend StreamException, so one catch covers the lot.

Unit tests

shell
composer test

Docker

shell
docker-compose up -d
docker exec -w /var/www/html -it quillstack_stream sh

License

MIT. See LICENSE.

Released under the MIT License.