Skip to content

Commit

Permalink
Improve Stream implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
nyamsprod committed Oct 15, 2023
1 parent e7162f9 commit a59fe0f
Show file tree
Hide file tree
Showing 3 changed files with 99 additions and 5 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ All Notable changes to `Csv` will be documented in this file
- `TabulatDataReader::firstOrFailMatching`
- `FragmentFinder` to implement [RFC7111](https://www.rfc-editor.org/rfc/rfc7111)
- `ResultSet::fromRecords`
- `Stream::setMaxLineLen`
- `Stream::getMaxLineLen`

### Deprecated

Expand All @@ -26,6 +28,7 @@ It's usage will trigger a `E_USER_DEPRECATED` call.
- `ResultSet` constructor now allows the records to be an `array`.
- to the internal `Stream` object it will throw a `RuntimeException` if the rewind action fails
- if calls to `fseek` fails (returns `-1` ) a new `RuntimeException` will be thrown too.
- `Stream` can iterate and return the full line respecting `SplFielObject` flags. Previously it only returned the CSV records.

### Removed

Expand Down
84 changes: 79 additions & 5 deletions src/Stream.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
use SplFileObject;
use Stringable;
use TypeError;
use ValueError;

use function array_keys;
use function array_walk_recursive;
Expand Down Expand Up @@ -67,6 +68,7 @@ final class Stream implements SeekableIterator
private string $escape = '\\';
/** @var array<string, array<resource>> Attached filters. */
private array $filters = [];
private int $maxLength = 0;

/**
* @param resource $stream stream type resource
Expand Down Expand Up @@ -295,7 +297,7 @@ public function rewind(): void

$this->offset = 0;
$this->value = false;
if (0 !== ($this->flags & SplFileObject::READ_AHEAD)) {
if (SplFileObject::READ_AHEAD === ($this->flags & SplFileObject::READ_AHEAD)) {
$this->current();
}
}
Expand All @@ -308,7 +310,7 @@ public function rewind(): void
public function valid(): bool
{
return match (true) {
0 !== ($this->flags & SplFileObject::READ_AHEAD) => false !== $this->current(),
SplFileObject::READ_AHEAD === ($this->flags & SplFileObject::READ_AHEAD) => false !== $this->current(),
default => !feof($this->stream),
};
}
Expand All @@ -324,24 +326,96 @@ public function current(): mixed
return $this->value;
}

$this->value = $this->getCurrentRecord();
$this->value = match (true) {
SplFileObject::READ_CSV === ($this->flags & SplFileObject::READ_CSV) => $this->getCurrentRecord(),
default => $this->getCurrentLine(),
};

return $this->value;
}

public function fgets(): string|false
{
$arg = [$this->stream];
if (0 < $this->maxLength) {
$arg[] = $this->maxLength;
}
return fgets(...$arg);
}

/**
* Sets the maximum length of a line to be read.
*
* @see https://www.php.net/manual/en/splfileobject.setmaxlinelen.php
*/
public function setMaxLineLen(int $maxLength): void
{
if (0 > $maxLength) {
throw new ValueError(' Argument #1 ($maxLength) must be greater than or equal to 0');
}

$this->maxLength = $maxLength;
}

/**
* Gets the maximum line length as set by setMaxLineLen.
*
* @see https://www.php.net/manual/en/splfileobject.getmaxlinelen.php
*/
public function getMaxLineLen(): int
{
return $this->maxLength;
}

/**
* Tells whether the end of file has been reached.
*
* @see https://www.php.net/manual/en/splfileobject.eof.php
*/
public function eof(): bool
{
return feof($this->stream);
}

/**
* Retrieves the current line as a CSV Record.
*/
private function getCurrentRecord(): array|false
{
$flag = 0 !== ($this->flags & SplFileObject::SKIP_EMPTY);
$isEmptyLine = SplFileObject::SKIP_EMPTY === ($this->flags & SplFileObject::SKIP_EMPTY);
do {
$ret = fgetcsv($this->stream, 0, $this->delimiter, $this->enclosure, $this->escape);
} while ($flag && is_array($ret) && null === $ret[0]);
} while ($isEmptyLine && is_array($ret) && null === $ret[0]);

return $ret;
}

/**
* Retrieves the current line.
*/
private function getCurrentLine(): string|false
{
$isEmptyLine = SplFileObject::SKIP_EMPTY === ($this->flags & SplFileObject::SKIP_EMPTY);
$dropNewLine = SplFileObject::DROP_NEW_LINE === ($this->flags & SplFileObject::DROP_NEW_LINE);
$shouldBeIgnored = fn (string|false $line): bool => ($isEmptyLine || $dropNewLine)
&& (false !== $line && '' === rtrim($line, "\r\n"));
$arguments = [$this->stream];
if (0 < $this->maxLength) {
$arguments[] = $this->maxLength;
}

do {
$line = fgets(...$arguments);
} while ($shouldBeIgnored($line));

if ($dropNewLine && false !== $line) {
return rtrim($line, "\r\n");
}

return $line;
}


/**
* Seeks to specified line.
*
Expand Down
17 changes: 17 additions & 0 deletions src/StreamTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,23 @@ public function testAppendStreamFilterThrowsException(): void
$stream = Stream::createFromPath('php://temp', 'r+');
$stream->appendFilter($filtername, STREAM_FILTER_READ);
}

public function testIterateOverLines(): void
{
$text = <<<TEXT
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
Duis nec sapien felis, ac sodales nisl.
Nulla vitae magna vitae purus aliquet consequat.
TEXT;
$newText = '';
$file = Stream::createFromString($text);
$file->setMaxLineLen(20);
foreach ($file as $line) {
$newText .= $line."\n";
}
self::assertStringContainsString('Lorem ipsum dolor s', $newText);
self::assertSame(20, $file->getMaxLineLen());
}
}

final class StreamWrapper
Expand Down

0 comments on commit a59fe0f

Please sign in to comment.