stream: adopt PSR-7 StreamInterface

This commit is contained in:
Henrik Hautakoski 2026-05-23 21:42:25 +02:00
parent 8d8690f77b
commit 89525294de
12 changed files with 250 additions and 152 deletions

View file

@ -2,8 +2,6 @@
use Shufflingpixels\IO\BinaryReader;
use Shufflingpixels\IO\Buffer;
use Shufflingpixels\IO\SeekMode;
use Shufflingpixels\IO\StreamInterface;
it('creates reader from stream and string helpers', function () {
$streamReader = BinaryReader::stream(new Buffer('abc'));
@ -24,7 +22,7 @@ it('proxies length tell eof and seek', function () {
expect($reader->tell())->toBe(2)
->and($reader->read(1))->toBe('c');
$reader->seek(-1, SeekMode::END);
$reader->seek(-1, SEEK_END);
expect($reader->read(1))->toBe('d')
->and($reader->eof())->toBeTrue();
});
@ -36,17 +34,22 @@ it('throws for negative read length', function () {
});
it('throws when stream returns fewer bytes than requested', function () {
$stream = new class implements StreamInterface {
$stream = new class implements \Psr\Http\Message\StreamInterface {
public function __toString(): string { return ''; }
public function close(): void {}
public function detach(): mixed { return null; }
public function getSize(): ?int { return 0; }
public function eof(): bool { return false; }
public function length(): int { return 0; }
public function isSeekable(): bool { return false; }
public function seek(int $position, Shufflingpixels\IO\SeekMode $mode = Shufflingpixels\IO\SeekMode::SET): void {}
public function seek(int $position, int $whence = SEEK_SET): void {}
public function rewind(): void {}
public function tell(): int { return 0; }
public function isWritable(): bool { return false; }
public function write(string $string): int { return 0; }
public function isReadable(): bool { return true; }
public function read(int $length): string { return 'x'; }
public function isWriteable(): bool { return false; }
public function write($data): int { return 0; }
public function getContents(): string { return ''; }
public function getMetadata(?string $key = null): mixed { return null; }
};
$reader = BinaryReader::stream($stream);

View file

@ -1,13 +1,11 @@
<?php
use Shufflingpixels\IO\Buffer;
use Shufflingpixels\IO\Exception\EndOfStreamException;
use Shufflingpixels\IO\SeekMode;
it('reads, seeks and tracks position', function () {
$buffer = new Buffer('abcdef');
expect($buffer->length())->toBe(6)
expect($buffer->getSize())->toBe(6)
->and($buffer->tell())->toBe(0)
->and($buffer->remaining())->toBe(6)
->and($buffer->eof())->toBeFalse();
@ -16,7 +14,7 @@ it('reads, seeks and tracks position', function () {
->and($buffer->tell())->toBe(2)
->and($buffer->remaining())->toBe(4);
$buffer->seek(-1, SeekMode::END);
$buffer->seek(-1, SEEK_END);
expect($buffer->tell())->toBe(5)
->and($buffer->read(1))->toBe('f')
@ -27,7 +25,7 @@ it('supports relative seek modes', function () {
$buffer = new Buffer('abcdef');
$buffer->seek(3);
$buffer->seek(-2, SeekMode::CUR);
$buffer->seek(-2, SEEK_CUR);
expect($buffer->tell())->toBe(1)
->and($buffer->read(2))->toBe('bc');
@ -45,10 +43,11 @@ it('throws for negative read length', function () {
expect(fn () => $buffer->read(-1))->toThrow(InvalidArgumentException::class);
});
it('throws when reading beyond end of stream', function () {
it('returns available bytes when reading beyond end of stream', function () {
$buffer = new Buffer('abc');
expect(fn () => $buffer->read(4))->toThrow(EndOfStreamException::class);
expect($buffer->read(4))->toBe('abc')
->and($buffer->eof())->toBeTrue();
});
it('writes at current position and updates contents', function () {
@ -68,5 +67,14 @@ it('writes nothing for empty payload', function () {
expect($buffer->write(''))->toBe(0)
->and($buffer->tell())->toBe(0)
->and($buffer->length())->toBe(3);
->and($buffer->getSize())->toBe(3);
});
it('returns remaining contents and advances cursor', function () {
$buffer = new Buffer('abcdef');
$buffer->seek(2);
expect($buffer->getContents())->toBe('cdef')
->and($buffer->tell())->toBe(6)
->and($buffer->getContents())->toBe('');
});

View file

@ -11,8 +11,8 @@ it('opens readable files and reads contents', function () {
$file = File::open($path, FileMode::READ);
expect($file->isReadable())->toBeTrue()
->and($file->isWriteable())->toBeFalse()
->and($file->length())->toBe(5)
->and($file->isWritable())->toBeFalse()
->and($file->getSize())->toBe(5)
->and($file->read(5))->toBe('hello');
$file->close();
@ -27,7 +27,7 @@ it('opens read-write files and persists writes', function () {
$file->seek(0);
expect($file->isReadable())->toBeTrue()
->and($file->isWriteable())->toBeTrue()
->and($file->isWritable())->toBeTrue()
->and($file->write('X'))->toBe(1);
$file->seek(0);
@ -44,8 +44,8 @@ it('opens write mode files and truncates existing contents', function () {
$file = File::open($path, FileMode::WRITE);
expect($file->isReadable())->toBeFalse()
->and($file->isWriteable())->toBeTrue()
->and($file->length())->toBe(0)
->and($file->isWritable())->toBeTrue()
->and($file->getSize())->toBe(0)
->and($file->write('xy'))->toBe(2);
$file->close();

View file

@ -15,11 +15,11 @@ it('reads, writes, seeks and reports stream state', function () {
}
};
expect($stream->length())->toBe(5)
expect($stream->getSize())->toBe(5)
->and($stream->tell())->toBe(0)
->and($stream->isSeekable())->toBeTrue()
->and($stream->isReadable())->toBeTrue()
->and($stream->isWriteable())->toBeTrue()
->and($stream->isWritable())->toBeTrue()
->and($stream->read(2))->toBe('he');
$stream->seek(0);
@ -27,7 +27,7 @@ it('reads, writes, seeks and reports stream state', function () {
$stream->seek(0);
expect($stream->read(5))->toBe('Hello')
->and($stream->length())->toBe(5)
->and($stream->getSize())->toBe(5)
->and($stream->eof())->toBeFalse();
$stream->read(1);
@ -46,7 +46,7 @@ it('throws when seeking or getting length on non-seekable stream', function () {
}
};
expect(fn () => $stream->length())->toThrow(IOException::class)
expect($stream->getSize())->toBeNull()
->and(fn () => $stream->seek(0))->toThrow(IOException::class);
$stream->close();

View file

@ -1,9 +0,0 @@
<?php
use Shufflingpixels\IO\SeekMode;
it('maps to native seek constants', function () {
expect(SeekMode::SET->value)->toBe(SEEK_SET)
->and(SeekMode::CUR->value)->toBe(SEEK_CUR)
->and(SeekMode::END->value)->toBe(SEEK_END);
});