Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[LazyStreamReader] Add LazyStreamReader #3

Merged
merged 1 commit into from
Jun 20, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -101,3 +101,22 @@ class GoogleCloudStorageLazyStreamFactory
}
}
```

### Reading lazily a stream with `LazyStreamReader`

Files are already read lazily by default: when you call `fread()`, you only fetch the number of bytes you asked for, not more.
`LazyStreamReader` does the same thing, but it also allows you to keep the stream open or not between reading operations.

For example, you may want to read a file 1MB by 1MB, and do some processing that may take some time each time. By setting the `autoClose` option to `true` when creating a new `LazyStreamReader` object, you ask to close the stream after each reading operation and open it again when the next reading operation is triggered. You'll be resumed at the same position you were in the stream before closing it.

```php
// The stream is not opened yet, in case you never need it
$stream = new \LazyStream\LazyStreamReader('https://user:pass@example.com/my-file.png', chunkSize: 1024, autoClose: true, binary: true);

// Use the stream directly in the loop
foreach ($stream as $str) {
// With auto-closing, the stream is already closed here. You can
// do any long operation, and the stream will be opened again when
// you get in the next loop iteration
}
```
70 changes: 70 additions & 0 deletions src/AbstractLazyStream.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
<?php

namespace LazyStream;

use LazyStream\Exception\LazyStreamOpenException;

abstract class AbstractLazyStream
{
/**
* @param resource|null $handle
*/
protected $handle;

protected ?array $metadata = null;

public function __construct(
protected string $uri,
protected string $openingMode,
) {
}

public function __destruct()
{
$this->closeStream();
}

protected function openStream(): void
{
if (!\is_resource($this->handle)) {
$this->handle = @\fopen($this->uri, $this->openingMode);

if ($this->handle === false) {
throw new LazyStreamOpenException($this->uri, $this->openingMode);
}

$this->metadata = \stream_get_meta_data($this->handle);
}
}

protected function closeStream(): void
{
if (\is_resource($this->handle)) {
\fclose($this->handle);
}

$this->handle = null;
}

/**
* @return resource|null
*/
public function getStreamHandle()
{
return $this->handle;
}

/**
* @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;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

namespace LazyStream\Exception;

class LazyStreamWriterOpenException extends AbstractLazyStreamWriterException
class LazyStreamOpenException extends AbstractLazyStreamWriterException
{
public function __construct(string $uri, string $mode)
{
Expand Down
71 changes: 71 additions & 0 deletions src/LazyStreamReader.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
<?php

/*
* (c) Alexandre Daubois <alex.daubois@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace LazyStream;

class LazyStreamReader extends AbstractLazyStream implements LazyStreamReaderInterface
{
private int $position = 0;

/**
* @param string $uri A valid stream URI.
* @param bool $autoClose Whether the stream should be closed between reading operations.
*/
public function __construct(
string $uri,
private int $chunkSize,
private bool $autoClose = true,
private bool $binary = false,
) {
parent::__construct($uri, $this->binary ? 'rb' : 'r');
}

public function getStreamPosition(): int
{
return $this->position;
}

public function isAutoClose(): bool
{
return $this->autoClose;
}

public function setAutoClose(bool $autoClose): void
{
$this->autoClose = $autoClose;
}

public function getIterator(): \Generator
{
yield from $this->read();
}

private function read(): \Generator
{
$this->openStream();

while (($data = \fread($this->handle, $this->chunkSize)) !== false && \strlen($data) !== 0) {
$this->position += $this->chunkSize;

if ($this->autoClose) {
$this->closeStream();
yield $data;

$this->openStream();
\fseek($this->handle, $this->position);

continue;
}

yield $data;
}

$this->closeStream();
}
}
14 changes: 14 additions & 0 deletions src/LazyStreamReaderInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<?php

/*
* (c) Alexandre Daubois <alex.daubois@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace LazyStream;

interface LazyStreamReaderInterface extends \IteratorAggregate
{
}
63 changes: 8 additions & 55 deletions src/LazyStreamWriter.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,40 +9,29 @@

namespace LazyStream;

use LazyStream\Exception\LazyStreamWriterOpenException;
use LazyStream\Exception\LazyStreamOpenException;
use LazyStream\Exception\LazyStreamWriterTriggerException;

/**
* A class to write to a stream lazily. The stream is only opened when
* the `trigger()` method is called. Data to write are provided by a
* generator, allowing data to be generated on the fly if possible.
*/
class LazyStreamWriter implements LazyStreamWriterInterface
class LazyStreamWriter extends AbstractLazyStream implements LazyStreamWriterInterface
{
/**
* @param resource|null $handle
*/
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 $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,
string $uri,
private \Iterator $dataProvider,
private string $openingMode = 'w',
string $openingMode = 'w',
private bool $autoClose = true,
) {
}

public function __destruct()
{
$this->closeStream();
parent::__construct($uri, $openingMode);
}

public function trigger(): void
Expand All @@ -66,14 +55,6 @@ public function trigger(): void
}
}

/**
* @return resource|null
*/
public function getStreamHandle()
{
return $this->handle;
}

public function unlink(): bool
{
if (!\is_resource($this->handle)) {
Expand All @@ -85,20 +66,6 @@ public function unlink(): bool
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;
Expand All @@ -114,26 +81,12 @@ 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
protected function closeStream(): void
{
if (\is_resource($this->handle)) {
\fflush($this->handle);
\fclose($this->handle);

$this->handle = null;
}

parent::closeStream();
}
}
5 changes: 0 additions & 5 deletions src/LazyStreamWriterInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,4 @@
interface LazyStreamWriterInterface
{
public function trigger(): void;

/**
* @return bool True if the stream has been unlinked, false otherwise.
*/
public function unlink(): bool;
}
Loading