IO
Input and output abstractions for PHP applications.
The IO package provides Input/Output abstractions to enable interoperability between multiple sources of bytes like streams, buffers, files and sockets.
Installation
Add the Castor repository to your composer.json
.
{ "repositories": [{ "type": "composer", "url": "https://castor-labs.github.io/php-packages" }]}
And then install with composer.
composer require castor/io
Motivation
PHP lacks of an proper abstractions to enable Input and Output interoperability. While PSR-7 StreamInterface
was a
step ahead, it massively violates the Interface Segregation principle by piling up too much behavior into the
interface.
This library defines a set of abstractions over Input and Output operations. There are 9 interfaces defined, along with an standard implementation over native PHP resources.
The Stream
API
The Stream API implemented by this library functions as a wrapper over the native PHP streams functions. The Castor\Io\Stream
class is capable of performing all the operations you can normally perform on a PHP resource. It doesn’t implement
every operation possible; just the most common ones. Current operations and their equivalent methods are:
\fread
:Castor\Io\Stream::read
\fwrite
:Castor\Io\Stream::write
\fclose
:Castor\Io\Stream::close
\fseek
:Castor\Io\Stream::seek
\fflush
:Castor\Io\Stream::flush
Creating a Stream
Creating a Castor\Io\Stream
is really easy. There are only two ways of doing it.
Castor\Io\Stream\open
is a proxy over \fopen
. It takes the path of the file to open and the mode.
use Castor\Io\Stream;
$stream = Stream\open('https://example.com', 'rb');$stream->read(10, $buff);
echo $buff; // The first 10 bytes of the source of of that website
Castor\Io\Stream\attach
takes an already created resource and puts it into the stream class.
use Castor\Io\Stream;
$stream = Stream\attach(STDOUT);$stream->write('Write something to the standard output');
Working with Castor\Io\Stream
Although the Castor\Io\Stream
class implements all the I/O operations possible, it doesn’t implement all the I/O
interfaces available in this library. This is because one of the design goals of this library is that the possible
operations on a stream are defined at a type-level.
This is the main difference with the approach that other abstractions over streams have in PHP. For instance, in the PSR
StreamInterface
we have all the possible I/O operations in a single interface, plus methods for checking if those
operations are supported on the stream. This breaks a lot of good object oriented design principles.
In order to allow for defining possible operations at a type level, while avoiding writing the logic multiple times,
there are 6 internal sub-types of Castor\Io\Stream
that implement the actual I/O interfaces. Those types
are selected at-runtime according to the capabilities found in the stream by the Castor\Io\Stream\attach
function.
For instance, if you call Castor\Io\Stream\attach
on STDIN
, you will get an instance of Castor\Io\Stream\Read
,
which extends Castor\Io\Stream
but also implements Castor\Io\Reader
. You can now use this stream to be a source of
bytes on any method or function that takes a Castor\Io\Reader
.
In the same fashion, if you call Castor\Io\Stream\attach
on the result of \fopen('/some/file.txt', 'w+b')
, you will
get an instance of Castor\Io\Stream\ReadWriteSeek
, which extends Castor\Io\Stream
, but also implements Castor\Io\Writer
,
Castor\Io\Reader
and Castor\Io\Seeker
. This is because the file was opened in read and write mode, and also because
files on the filesystem can be sought.
This capability checking over your streams at runtime makes your application safer by giving you the assurance that the source of data you are dealing with can be read, or written or sought. Also, makes your code easier to test since you are not coupled to an implementation. Finally, it makes your code easier to reason about and work with because the abstractions are simple, and not massive interfaces with 10+ methods.
The Castor\Io\Stream\inMemory
function
Tremendously useful in tests, this function returns a Castor\Io\Stream\ReadWriteSeek
that can be used to do pretty
much any kind of I/O operation. It takes an optional argument called $data
to initialize the stream with that data. The
stream is automatically rewinded when that initial data is written. The underlying resource follows the rules of the
php://memory
stream.
use Castor\Io\Stream;
$stream = Stream\inMemory('data');echo $n = $stream->read(4, $buff); // 4echo $buff; // "data"
The Castor\Io
interfaces
I/O operations have been abstracted in 6 main separate interfaces. It’s important you study them, because these abstractions are used pretty much everywhere in the Castor Packages.
The Castor\Io\Reader
interface
The Castor\Io\Reader
interface is implemented by all types that can read bytes from a source. The read method
takes the number of bytes you want to read, and then the buffer where to allocate those bytes (passed by reference).
The method returns the number of bytes read.
use Castor\Io\Stream;
$stream = Stream\inMemory('Hello World');echo $stream->read(5, $read); // 5echo $read; // "Hello"
When there are no more bytes to read, Castor\Io\Reader::read
will throw a Castor\Io\EndOfFile
exception.
use Castor\Io\Stream;
$stream = Stream\inMemory('Hello World');echo $stream->read(11, $read); // 11echo $read; // "Hello World"
// This call will throw a Castor\Io\EndOfFile exception.$stream->read(1, $read);
Any other error in reading (the source is closed or it cannot be read) will throw a Castor\Io\Error
exception.
Reading files in this manner is of course very time consuming when you just want to get all the data in a stream. For this we provide an special utility function.
The Castor\Io\Writer
interface
TODO
The Castor\Io\Closer
interface
TODO
The Castor\Io\Seeker
interface
TODO
The Castor\Io\Flusher
interface
TODO
The Castor\Io\ReadAt
interface
TODO
The Castor\Io\WriteFrom
interface
TODO
Functions and Utilities
This library provides a series of functions and utilities to ease some of the most common tasks when performing some I/O operations.
The Castor\Io\readAll
function
This function takes a Castor\Io\Reader
and reads all of it’s contents into memory until Castor\Io\EndOfFile
is
thrown.
use Castor\Io\Stream;use Castor\Io;
$stream = Stream\open('https://example.com', 'rb');$contents = Io\readAll($stream); // Puts all the contents in memory