Skip to content

Commit

Permalink
- Larger refactoring
Browse files Browse the repository at this point in the history
  • Loading branch information
TorbenKoehn committed Jan 22, 2019
1 parent 72e7f85 commit 5d68f40
Show file tree
Hide file tree
Showing 40 changed files with 1,060 additions and 446 deletions.
263 changes: 216 additions & 47 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,13 @@ Tale Stream
What is Tale Stream?
--------------------

This is an implementation of the `Psr\HttpMessage\StreamInterface`. It doesn't add any
extra methods, only a few utility stream classes that extend it.
This is a direct implementation of the PSR-7 `Psr\HttpMessage\StreamInterface` and
the PSR-17 `Psr\Http\Message\StreamFactoryInterface`. It doesn't add any
extra methods, only a few utility stream classes and some useful
iterators for streams.

You can use it as a base for any kind of full-fledged stream implementation.
It acts as a stable streaming library core for full-fledged streaming libraries
like socket implementations or filesystem abstractions.

Installation
------------
Expand All @@ -25,6 +28,9 @@ composer require talesoft/tale-stream
Usage
-----

The heart is the `Tale\Stream` class. You can pass any resource
of type `stream` and have a PSR-7 compatible stream for any purpose.

```php
use Tale\Stream;

Expand All @@ -37,14 +43,100 @@ if ($stream->isReadable()) {
if ($stream->isWritable()) {
$stream->write('Some Content');
}

```

### Using the factory
### Utility Streams

#### FileStream

`use Tale\Stream\FileStream;`

The filestream works analogous to the `fopen($filename, $mode, $useIncludePath, $context)` function

```php
$fs = new FileStream(__DIR__.'/some-file.txt', 'rb');
while (!$fs->eof()) {
echo $fs->read(32);
}
```

#### InputStream

`use Tale\Stream\InputStream;`

The input stream is a readable FileStream on `php://input`

#### MemoryStream

`use Tale\Stream\MemoryStream;`

The memory stream is a readable and writable FileStream on `php://memory`. Very useful
to provide string-based values to stream-based operations. You can feed it with initial
data:

```php
$ms = new MemoryStream('I am some content!');

echo $ms->read(10); //"I am some "
echo $ms->read(8); //"content!"
```

#### NullStream

`use Tale\Stream\NullStream;`

The null-stream does nothing. It only implements the interfaces. Useful
to avoid defensive null checks on optional dependencies, to suppress things
and for testing.

#### OutputStream

`use Tale\Stream\OutputStream;`

The output stream is a writable FileStream on `php://output`

#### StandardErrorStream

`use Tale\Stream\StandardErrorStream;`

The standard error stream is a writable FileStream on `php://stderr`

#### StandardInputStream

`use Tale\Stream\StandardInputStream;`

The standard input stream is a readable FileStream on `php://stdin`

#### StandardOutputStream

`use Tale\Stream\StandardOutputStream;`

The standard output stream is a writable FileStream on `php://stdout`

#### TempStream

`use Tale\Stream\TempStream;`

The temp stream is a FileStream on `php://temp`. You can set a maximum
memory limit (when the stream starts using a temporary file) and you can
feed it with initial data, too:

```php
use Tale\Stream\Factory;
$ts = new TempStream('I am some content!', 1024);

echo $ts->read(10); //"I am some "
echo $ts->read(8); //"content!"
```

### Using the factory

`use Tale\Stream\Factory;`

The factory is an implementation of the PSR-17 standard
and provides a way to have a centralized factory for streams
in your dependency injection container.

```php
$factory = new Factory();

$stream = $factory->createStream('some stream content');
Expand All @@ -54,47 +146,111 @@ $stream = $factory->createStreamFromFile('/some/file', 'rb+');
$stream = $factory->createStreamFromResource(fopen('/some/file', 'rb+'));
```

### Using the iterators
If you have a Dependency Injection container, you can inject
the factory if you registered it as a service

ReadIterator will read a stream chunk-by-chunk (default chunk size is 1024)
```php
use Psr\Http\Message\StreamFactoryInterface;

class MyService
{
private $streamFactory;

public function __construct(StreamFactoryInterface $streamFactory)
{
$this->streamFactory = $streamFactory;
}

public function doStuff(): void
{
$stream = $this->streamFactory->createStreamFromFile(
__DIR__.'/some-file'
);

//etc. etc.
}
}
```

#### Roll your own stream factory

`use Tale\Stream\FactoryTrait;`

If you already have some kind of stream or HTTP factory
or want to roll your own one, there is a trait for you to use
that basically gives you the full functionality of the
default factory.

```php
use Tale\Stream\MemoryStream;
use Tale\Stream\Iterator\ReadIterator;
use Psr\Http\Message\StreamFactoryInterface;

class MyStreamFactory implements StreamFactoryInterface
{
use FactoryTrait;

//other implementations and stuff
}
```

You can then register it in your DI container and all services
will start using your own implementation.

### Utility iterators

Tale Stream provides some basic iterators to make reading streams
easier by default.


#### ReadIterator

`use Tale\Stream\Iterator\ReadIterator`

ReadIterator will read a stream chunk-by-chunk (default chunk size is 1024)

```php
//Have a stream of some kind
$stream = new MemoryStream('abcdefg');

$reader = new ReadIterator($stream, 2);
//Create a ReadIterator on that stream
$iterator = new ReadIterator($stream, 2); //2 is the chunk size (default: 1024)

foreach ($reader as $chunk) {
//Just iterate the reader to get all chunks
foreach ($iterator as $chunk) {
var_dump($chunk); //0 => ab, 1 => cd, 2 => ef, 3 => g
}

//Alternatively get all chunks into an array
$chunks = iterator_to_array($iterator);
```

SplitIterator will split the content by a delimiter and yield item-by-item
#### SplitIterator

```php
use Tale\Stream\MemoryStream;
use Tale\Stream\Iterator\ReadIterator;
use Tale\Stream\Iterator\SplitIterator;
`use Tale\Stream\Iterator\SplitIterator`

SplitIterator will split the content by a delimiter and yield item-by-item.
You can pass any iterator as the first argument.

```php
$stream = new MemoryStream('ab|cd|ef|g');

$reader = new SplitIterator($stream, '|');
$readIterator = new ReadIterator($stream);

$reader = new SplitIterator($readIterator, '|');

foreach ($reader as $item) {
var_dump($item); //0 => ab, 1 => cd, 2 => ef, 3 => g
}
```

LineIterator will split the stream content by lines and yield line-by-line
#### LineIterator

```php
`use Tale\Stream\Iterator\LineIterator`

use Tale\Stream\MemoryStream;
use Tale\Stream\Iterator\ReadIterator;
use Tale\Stream\Iterator\LineIterator;
LineIterator will split the stream content by lines and yield line-by-line.
Other than `fgets`, this line reader is not limited to its chunk-size. Lines
can be as long as the PHP process allows and the chunk size does not matter.

```php
$stream = new MemoryStream("ab\ncd\nde\ng");

$reader = new LineIterator($stream);
Expand All @@ -104,26 +260,52 @@ foreach ($reader as $item) {
}
```

### Piping streams with iterators
### WriteIterator

`use Tale\Stream\Iterator\WriteIterator`

WriteIterator allows to pipe and filter streams easily and efficiently.

```php
use Tale\Stream\MemoryStream;
use Tale\Stream\Iterator\ReadIterator;
use Tale\Stream\Iterator\WriteIterator;
$inputStream = new MemoryStream('stream content');

$inputStream = new MemoryStream();
$outputStream = new MemoryStream();

$chunkSize = 2048;

$pipe = new WriteIterator($outputStream, new ReadIterator($inputStream, $chunkSize));
foreach ($pipe as $writtenBytes) { //The actual piping process, chunk-by-chunk
$readIterator = new ReadIterator($inputStream, $chunkSize);

$iterator = new WriteIterator($outputStream, $readIterator);

//Write the whole stream at once
var_dump($iterator->writeAll()); //int(14)

//Write the stream sequentially, chunk-by-chunk
foreach ($iterator as $writtenBytes) {
echo "Wrote {$writtenBytes} bytes";
}
```

The WriteIterator can take any iterable as its source, so you can
also pipe things like generators or even arrays

```php
function generateContents(): Generator
{
yield "Line 1\n";
yield "Line 2\n";
yield "Line 3\n";
}

$stream = new FileStream('./some-file.txt', 'wb');

$iterator = new WriteIterator($stream, generateContents());
$iterator->writeAll();

//alternatively you can use iterator_to_array to pipe the whole stream at once
$writtenBytesArray = iterator_to_array($pipe);
//./some-file.txt now contains:
// Line 1\n
// Line 2\n
// Line 3\n
```

Using iterators you can filter streams during piping in many different ways
Expand Down Expand Up @@ -151,22 +333,9 @@ $lfSuffixer = new SuffixIterator($filteredReader, "\n");

$outputStream = new MemoryStream();

$pipe = new WriteIterator($outputStream, $lfSuffixer);
$writtenBytes = iterator_to_array($pipe); //The actual piping process, chunk-by-chunk
$writer = new WriteIterator($outputStream, $lfSuffixer);
$writer->writeAll();

var_dump((string)$outputStream); //"ab\ncd\ng"
```



### Available Streams

- `Tale\Stream\FileStream` -> Same API as `fopen`
- `Tale\Stream\InputStream` -> php://input, rb
- `Tale\Stream\OutputStream` -> php://output, wb
- `Tale\Stream\MemoryStream` -> php://memory, rb+
- `Tale\Stream\TempStream` -> php://temp, rb+
- `Tale\Stream\NullStream` -> Empty readable, writable and seekable stream that implements the interfaces, but does nothing at all
- `Tale\Stream\StandardErrorStream` -> STDERR wrapper
- `Tale\Stream\StandardInputStream` -> STDIN wrapper
- `Tale\Stream\StandardOutputStream` -> STDOUT wrapper
Loading

0 comments on commit 5d68f40

Please sign in to comment.