Input- and OutputStreams.
stubbles/streams is distributed as Composer package. To install it as a dependency of your package use the following command:
composer require "stubbles/streams": "^10.0"
stubbles/streams requires at least PHP 8.2.
For using encoding and decoding decorated streams the PHP extension iconv is required.
For the stubbles\streams\linesOf()
and stubbles\streams\nonEmptyLinesOf()
functions the package stubbles/sequence
is required.
stubbles/streams provides input and output streams for different kind of
sources. All input streams implement the stubbles\streams\InputStream
interface, all output streams the stubbles\streams\OutputStream
interface.
While input streams can be used to read data from a source, output streams can
be used to write data to a certain source.
The input stream interface provides the following methods:
read($length = 8192)
- returns given amount of bytesreadLine($length = 8192)
- returns all characters up to given length until next line breakbytesLeft()
- returns the amount of bytes left to be readeof()
- checks whether end of input was reachedclose()
- closes the stream
The output stream interface provides the following methods:
write($bytes)
- writes given bytes and returns amount of written byteswriteLine($bytes)
- same aswrite()
, but adds a line break at end of bytesclose()
- closes the stream
Some streams are seekable which means you can go from one position in the stream
to another. Input streams which are seekable implement the
stubbles\streams\Seekable
interface. It provides the following methods:
seek($offset, $whence = Seekable::SET)
- sets internal stream pointer to given positiontell()
- returns the current position of the internal stream pointer
Available since release 8.1.0
The shortcut function stubbles\streams\copy()
provides a simple way to copy everything what's
in an input stream to an output stream:
copy($in)->to($out);
Please note that copying starts at the offset where the input stream currently is located, and changes the offset of the input stream to the end of the stream. In case a non-seekable input stream is copied it can not return to its initial offset.
Sometimes it is necessary to decode input stream data into the internal encoding
of the application. While Stubbles' internal encoding is UTF-8, all data read
from input streams should be UTF-8 itself or at least converted to UTF-8 before
returned from the input stream. To ease this, the
stubbles\streams\DecodingInputStream
class decorates another input stream
given as first parameter on construction, and tries to decode the data read
from the decorated input stream from the stream encoding to UTF-8 using iconv()
.
However you need to specify the charset of the decorated input stream as second
parameter to the constructor:
$decodingInputStream = new DecodingInputStream($encodedInputStream, 'iso-8859-1');
Note: since release 9.0.0 a third parameter can be used to influence what the data is being encoded to. The default is still UTF-8.
Of course there must be a possibility to write back into the correct encoding.
For this, the stubbles\streams\EncodingOutputStream
class can be used. It
tries to convert from internal UTF-8 into the encoding of the decorated output
stream using iconv()
.
$encodingOutputStream = new EncodingOutputStream($encodedOutputStream, 'iso-8859-1');
Note: since release 9.0.0 a third parameter can be used to influence what the data is being encoded from. The default is still UTF-8.
To read data from a file one may use the stubbles\streams\file\FileInputStream
class. Its constructor expects either a name of a file, or an already opened
file pointer resource. If it is the name of the file the second parameter of the
constructor sets the mode in which the file is opened, it defaults to rb
(binary reading). The file input stream is a seekable stream.
To write data to a file one may use the stubbles\streams\file\FileOutputStream
class. Similarly to the file input stream class its constructor expects either a
name of a file, or an already opened file pointer resource. If it is the name of
the file the second parameter of the constructor sets the mode in which the file
is opened, it defaults to wb (binary writing).
Warning: the mode parameter accepts modes which might not make any sense with the stream class - e.g. the input stream allows wb as value for the mode parameter, but then you can not read from the given file, and vice versa for the output stream.
Sometimes it is helpful if one can read or write data into memory. For such
purposes the stubbles\streams\memory\MemoryInputStream
and
stubbles\streams\memory\MemoryOutputStream
exist. While the memory input
stream class expects the content to be read from as string parameter for its
constructor, the memory output stream does not expect a value on construction,
but offers an additional method buffer()
which returns all data written to
this stream so far. Additionally, casting the stubbles\streams\memory\MemoryOutputStream
to string will return the buffer (available since release 4.0.0).
The memory input stream is a seekable stream.
When reading from or writing data to a stream sometimes it may happen that not all data to read or not all data to be written is relevant for what you want to achieve. For example, a file you read may contain comment lines, and you don't want to ignore those comment lines. Normally, you would have the logic on what to ignore in your reading class:
while (!$inputStream->eof()) {
$line = $inputStream->readLine();
if (substr(0, 1, $line) !== '#') {
$this->processLine($line);
}
}
In this small example it may not be much work, but what if comment lines may
also start with //
or there may even be comments stretching over more than one
line as we have in PHP with /* comment over several lines ... */
. Now the
logic might get a bit to complicated at this point. Stream filters to the rescue:
$filterStream = new FilteredInputStream(
$inputStream,
function($line) { return substr($line, 0, 1) !== '#'; }
);
while (!$filterStream->eof()) {
$this->processLine($inputStream->readLine());
}
The second argument for stubbles\streams\filter\FilteredInputStream
can be any
callable
which accepts a string as argument and returns true
when this string
should be passed through, and false
when this string should be filtered. Please
note that you can't filter single characters from the passed string, only
characters as a whole.
The same applies to the stubbles\streams\filter\FilteredOutputStream
,
just that the filter is applied to the data that is written.
Because iterating over input streams with while
can be quite cumbersome,
stubbles/streams provides an input stream implementation which is also an
instance of \Iterator
.
$lines = new InputStreamIterator(new FileInputStream('somefile.txt'));
foreach ($lines as $line {
processLine($line);
}
Each iteration step is a call to readLine()
of the decorated input stream.
Please note: when the decorated stream is not an instance of stubbles\streams\Seekable
it can be iterated only once, trying to rewind will do nothing.
Optionally iteration can be even more enhanced when the package stubbles/sequence is available.
This allows to use two functions which return an instance of
stubbles\sequence\Sequence
that allow all sequence operation on an input
stream:
The input can either be an instance of stubbles\streams\InputStream
or a file
name.
$lines = linesOf('somefile.txt')
->filter(/* callable which filters */)
->map(/* callable which maps the line to other content */);
foreach ($lines as $line) {
processLine($line);
}
Same as above, but already filters all empty lines.