diff --git a/README.md b/README.md index 84c03a0..a78b758 100644 --- a/README.md +++ b/README.md @@ -24,7 +24,7 @@ composer require alexandre-daubois/lazy-stream ## Usage -### Writing lazily to a stream +### Writing lazily to a stream with `LazyStreamWriter` ```php function provideJsonData(): \Generator @@ -49,9 +49,20 @@ $stream = new \LazyStream\LazyStreamWriter( // Trigger the stream to *actually* initiate connection // and unwrap the iterator $stream->trigger(); + +// Fetch stream's metadata, which will also be done lazily. It is +// *not* required to call `trigger()` to get those data. +$metadata = $stream->getMetadata(); ``` -### Usage with third-party libraries +### Configuring `LazyStreamWriter` behavior + +A few options are available to configure how à lazy stream should behave: + +- Opening mode: this allows to define the mode that will be used to open the stream. Any writing mode [listed here](https://www.php.net/manual/en/function.fopen.php) can be used. +- Auto-closing: whether the stream should be automatically flushed and closed at the end of the `trigger()` method. If set to false, the stream will be flushed and closed in any case when the `LazyStreamWriter` object is destroyed. + +## Usage with third-party libraries This library also works well with third-party libraries. For example, you can combine it with the [google/cloud-storage](https://packagist.org/packages/google/cloud-storage) package to write big files to your buckets without having to worry about memory problems (among other things). diff --git a/src/LazyStreamWriter.php b/src/LazyStreamWriter.php index 64cd7b4..0f2fd52 100644 --- a/src/LazyStreamWriter.php +++ b/src/LazyStreamWriter.php @@ -24,36 +24,30 @@ class LazyStreamWriter implements LazyStreamWriterInterface */ private $handle; + private ?array $metadata = null; + /** * @param string $uri A valid stream URI. * @param \Iterator $dataProvider The data provider that will be written to the stream. - * @param string $openMode A valid writing mode listed in https://www.php.net/manual/fr/function.fopen.php. + * @param string $openingMode A valid writing mode listed in https://www.php.net/manual/fr/function.fopen.php. * @param bool $autoClose Whether the stream should be closed once the `trigger` method is done. */ public function __construct( private string $uri, private \Iterator $dataProvider, - private string $openMode = 'w', + private string $openingMode = 'w', private bool $autoClose = true, ) { } public function __destruct() { - if (\is_resource($this->handle)) { - \fclose($this->handle); - } + $this->closeStream(); } public function trigger(): void { - if (!\is_resource($this->handle)) { - $this->handle = @\fopen($this->uri, $this->openMode); - - if ($this->handle === false) { - throw new LazyStreamWriterOpenException($this->uri, $this->openMode); - } - } + $this->openStream(); try { while ($this->dataProvider->valid()) { @@ -66,10 +60,8 @@ public function trigger(): void } catch (\Throwable $throwable) { throw new LazyStreamWriterTriggerException(previous: $throwable); } finally { - if (\is_resource($this->handle) && $this->autoClose) { - \fclose($this->handle); - - $this->handle = null; + if ($this->autoClose) { + $this->closeStream(); } } } @@ -88,11 +80,25 @@ public function unlink(): bool return true; } - $this->handle = null; + $this->closeStream(); return \unlink($this->uri); } + /** + * @return array Stream meta-data array indexed by keys given in https://www.php.net/manual/en/function.stream-get-meta-data.php. + */ + public function getMetadata(): array + { + if ($this->metadata === null) { + // If metadata is null, then we never opened the stream yet + $this->openStream(); + $this->closeStream(); + } + + return $this->metadata; + } + public function equals(self $other): bool { return $this->dataProvider === $other->dataProvider && $this->uri === $other->uri; @@ -107,4 +113,27 @@ public function setAutoClose(bool $autoClose): void { $this->autoClose = $autoClose; } + + private function openStream(): void + { + if (!\is_resource($this->handle)) { + $this->handle = @\fopen($this->uri, $this->openingMode); + + if ($this->handle === false) { + throw new LazyStreamWriterOpenException($this->uri, $this->openingMode); + } + } + + $this->metadata = \stream_get_meta_data($this->handle); + } + + private function closeStream(): void + { + if (\is_resource($this->handle)) { + \fflush($this->handle); + \fclose($this->handle); + + $this->handle = null; + } + } } diff --git a/tests/LazyStreamWriterTest.php b/tests/LazyStreamWriterTest.php index 98c91d8..8e8fce0 100644 --- a/tests/LazyStreamWriterTest.php +++ b/tests/LazyStreamWriterTest.php @@ -124,4 +124,33 @@ public function testTriggersThrowsOnUnwrappingWithoutAutoClose(): void throw $exception; } } + + public function testGetType(): void + { + $lazyStream = new LazyStreamWriter('php://memory', new \ArrayIterator([])); + + $this->assertSame('MEMORY', $lazyStream->getMetadata()['stream_type']); + $this->assertNull($lazyStream->getStreamHandle()); + } + + public function testGetTypeOnTriggeredStreamWithoutAutoclose(): void + { + $lazyStream = new LazyStreamWriter('php://memory', new \ArrayIterator([]), autoClose: false); + + $lazyStream->trigger(); + + $this->assertSame('MEMORY', $lazyStream->getMetadata()['stream_type']); + $this->assertNotNull($lazyStream->getStreamHandle()); + } + + public function testGetTypeOnTriggeredStreamWithAutoclose(): void + { + $lazyStream = new LazyStreamWriter('php://memory', new \ArrayIterator([]), autoClose: true); + + $lazyStream->trigger(); + $this->assertNull($lazyStream->getStreamHandle()); + + $this->assertSame('MEMORY', $lazyStream->getMetadata()['stream_type']); + $this->assertNull($lazyStream->getStreamHandle()); + } }