mirror of
https://github.com/shufflingpixels/php-io.git
synced 2026-08-15 11:48:13 +02:00
Add documentation to classes and methods
This commit is contained in:
parent
4bf5d8084e
commit
eadc96599b
9 changed files with 362 additions and 8 deletions
|
|
@ -9,12 +9,23 @@ use InvalidArgumentException;
|
|||
use Psr\Http\Message\StreamInterface;
|
||||
use RuntimeException;
|
||||
|
||||
/**
|
||||
* Reads primitive binary types from a PSR-7 stream.
|
||||
*
|
||||
* Integer methods follow the naming convention read{Signedness}{Bits}{Endianness},
|
||||
* e.g. readInt16LE for a signed 16-bit little-endian integer.
|
||||
*/
|
||||
class BinaryReader
|
||||
{
|
||||
public function __construct(protected StreamInterface $stream)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the total byte length of the stream.
|
||||
*
|
||||
* @throws \RuntimeException if the stream size is not known
|
||||
*/
|
||||
public function length() : int
|
||||
{
|
||||
$size = $this->stream->getSize();
|
||||
|
|
@ -25,24 +36,37 @@ class BinaryReader
|
|||
return $size;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current byte offset of the stream cursor.
|
||||
*/
|
||||
public function tell() : int
|
||||
{
|
||||
return $this->stream->tell();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true when the cursor is at the end of the stream.
|
||||
*/
|
||||
public function eof() : bool
|
||||
{
|
||||
return $this->stream->eof();
|
||||
}
|
||||
|
||||
/**
|
||||
* Moves the stream cursor to the given position.
|
||||
*
|
||||
* @param int $whence SEEK_SET, SEEK_CUR, or SEEK_END
|
||||
*/
|
||||
public function seek(int $position, int $whence = SEEK_SET): void
|
||||
{
|
||||
$this->stream->seek($position, $whence);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws \InvalidArgumentException
|
||||
* @throws \RuntimeException
|
||||
* Reads exactly $length bytes, throwing if fewer are available.
|
||||
*
|
||||
* @throws \InvalidArgumentException if $length is negative
|
||||
* @throws \RuntimeException if the stream returns fewer bytes than requested
|
||||
*/
|
||||
public function read(int $length): string
|
||||
{
|
||||
|
|
@ -60,11 +84,17 @@ class BinaryReader
|
|||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads an unsigned 8-bit integer (0–255).
|
||||
*/
|
||||
public function readUInt8(): int
|
||||
{
|
||||
return \ord($this->read(1));
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads a signed 8-bit integer (-128–127).
|
||||
*/
|
||||
public function readInt8(): int
|
||||
{
|
||||
$value = $this->readUInt8();
|
||||
|
|
@ -72,16 +102,25 @@ class BinaryReader
|
|||
return $value >= 0x80 ? $value - 0x100 : $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads an unsigned 16-bit little-endian integer.
|
||||
*/
|
||||
public function readUInt16LE(): int
|
||||
{
|
||||
return \unpack('v', $this->read(2))[1];
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads an unsigned 16-bit big-endian integer.
|
||||
*/
|
||||
public function readUInt16BE(): int
|
||||
{
|
||||
return \unpack('n', $this->read(2))[1];
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads a signed 16-bit little-endian integer.
|
||||
*/
|
||||
public function readInt16LE(): int
|
||||
{
|
||||
$value = $this->readUInt16LE();
|
||||
|
|
@ -89,6 +128,9 @@ class BinaryReader
|
|||
return $value >= 0x8000 ? $value - 0x1_0000 : $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads a signed 16-bit big-endian integer.
|
||||
*/
|
||||
public function readInt16BE(): int
|
||||
{
|
||||
$value = $this->readUInt16BE();
|
||||
|
|
@ -96,16 +138,25 @@ class BinaryReader
|
|||
return $value >= 0x8000 ? $value - 0x1_0000 : $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads an unsigned 32-bit little-endian integer.
|
||||
*/
|
||||
public function readUInt32LE(): int
|
||||
{
|
||||
return \unpack('V', $this->read(4))[1];
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads an unsigned 32-bit big-endian integer.
|
||||
*/
|
||||
public function readUInt32BE(): int
|
||||
{
|
||||
return \unpack('N', $this->read(4))[1];
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads a signed 32-bit little-endian integer.
|
||||
*/
|
||||
public function readInt32LE(): int
|
||||
{
|
||||
$value = $this->readUInt32LE();
|
||||
|
|
@ -113,6 +164,9 @@ class BinaryReader
|
|||
return $value >= 0x8000_0000 ? $value - 0x1_0000_0000 : $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads a signed 32-bit big-endian integer.
|
||||
*/
|
||||
public function readInt32BE(): int
|
||||
{
|
||||
$value = $this->readUInt32BE();
|
||||
|
|
@ -121,7 +175,9 @@ class BinaryReader
|
|||
}
|
||||
|
||||
/**
|
||||
* Reads a string of fixed length, trimming any padding characters from the end.
|
||||
* Reads exactly $length bytes and strips trailing $pad_chars from the result.
|
||||
*
|
||||
* The cursor always advances by $length bytes regardless of how much padding is trimmed.
|
||||
*/
|
||||
public function readPaddedString(int $length, string $pad_chars = "\x00") : string
|
||||
{
|
||||
|
|
|
|||
|
|
@ -8,79 +8,133 @@ namespace Shufflingpixels\IO;
|
|||
use InvalidArgumentException;
|
||||
use Psr\Http\Message\StreamInterface;
|
||||
|
||||
/**
|
||||
* Writes primitive binary types to a PSR-7 stream.
|
||||
*
|
||||
* Integer methods follow the naming convention write{Signedness}{Bits}{Endianness},
|
||||
* e.g. writeInt16LE for a signed 16-bit little-endian integer.
|
||||
*
|
||||
* All write methods return the number of bytes written, as forwarded from the stream.
|
||||
*/
|
||||
class BinaryWriter
|
||||
{
|
||||
public function __construct(protected StreamInterface $stream)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current byte offset of the stream cursor.
|
||||
*/
|
||||
public function tell(): int
|
||||
{
|
||||
return $this->stream->tell();
|
||||
}
|
||||
|
||||
/**
|
||||
* Moves the stream cursor to the given position.
|
||||
*
|
||||
* @param int $whence SEEK_SET, SEEK_CUR, or SEEK_END
|
||||
*/
|
||||
public function seek(int $position, int $whence = SEEK_SET): void
|
||||
{
|
||||
$this->stream->seek($position, $whence);
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes raw bytes to the stream.
|
||||
*/
|
||||
public function write(string $data): int
|
||||
{
|
||||
return $this->stream->write($data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes an unsigned 8-bit integer (0–255).
|
||||
*/
|
||||
public function writeUInt8(int $value): int
|
||||
{
|
||||
return $this->write(\chr($value & 0xff));
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes a signed 8-bit integer (-128–127).
|
||||
*/
|
||||
public function writeInt8(int $value): int
|
||||
{
|
||||
return $this->writeUInt8($value < 0 ? $value + 0x100 : $value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes an unsigned 16-bit little-endian integer.
|
||||
*/
|
||||
public function writeUInt16LE(int $value): int
|
||||
{
|
||||
return $this->write(\pack('v', $value));
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes an unsigned 16-bit big-endian integer.
|
||||
*/
|
||||
public function writeUInt16BE(int $value): int
|
||||
{
|
||||
return $this->write(\pack('n', $value));
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes a signed 16-bit little-endian integer.
|
||||
*/
|
||||
public function writeInt16LE(int $value): int
|
||||
{
|
||||
return $this->writeUInt16LE($value < 0 ? $value + 0x1_0000 : $value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes a signed 16-bit big-endian integer.
|
||||
*/
|
||||
public function writeInt16BE(int $value): int
|
||||
{
|
||||
return $this->writeUInt16BE($value < 0 ? $value + 0x1_0000 : $value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes an unsigned 32-bit little-endian integer.
|
||||
*/
|
||||
public function writeUInt32LE(int $value): int
|
||||
{
|
||||
return $this->write(\pack('V', $value));
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes an unsigned 32-bit big-endian integer.
|
||||
*/
|
||||
public function writeUInt32BE(int $value): int
|
||||
{
|
||||
return $this->write(\pack('N', $value));
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes a signed 32-bit little-endian integer.
|
||||
*/
|
||||
public function writeInt32LE(int $value): int
|
||||
{
|
||||
return $this->writeUInt32LE($value < 0 ? $value + 0x1_0000_0000 : $value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes a signed 32-bit big-endian integer.
|
||||
*/
|
||||
public function writeInt32BE(int $value): int
|
||||
{
|
||||
return $this->writeUInt32BE($value < 0 ? $value + 0x1_0000_0000 : $value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes a string padded or truncated to exactly $length bytes.
|
||||
* Writes $data padded or truncated to exactly $length bytes.
|
||||
*
|
||||
* Strings shorter than $length are right-padded with $pad_char.
|
||||
* Strings longer than $length are truncated to $length bytes.
|
||||
*
|
||||
* @throws \InvalidArgumentException if $pad_char is not exactly one byte
|
||||
*/
|
||||
public function writePaddedString(string $data, int $length, string $pad_char = "\x00"): int
|
||||
{
|
||||
|
|
|
|||
|
|
@ -10,6 +10,12 @@ use OutOfBoundsException;
|
|||
use Psr\Http\Message\StreamInterface;
|
||||
use RuntimeException;
|
||||
|
||||
/**
|
||||
* An in-memory PSR-7 stream backed by a plain PHP string.
|
||||
*
|
||||
* Supports both reading and writing. Writing at the current cursor position
|
||||
* overwrites existing bytes and extends the buffer if the write goes past the end.
|
||||
*/
|
||||
class Buffer implements StreamInterface
|
||||
{
|
||||
private int $position = 0;
|
||||
|
|
@ -19,11 +25,17 @@ class Buffer implements StreamInterface
|
|||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Detaches and discards the internal string.
|
||||
*/
|
||||
public function close(): void
|
||||
{
|
||||
$this->detach();
|
||||
}
|
||||
|
||||
/**
|
||||
* Detaches the internal string, clearing its contents, and returns null.
|
||||
*/
|
||||
public function detach(): mixed
|
||||
{
|
||||
if ($this->detached) {
|
||||
|
|
@ -37,16 +49,25 @@ class Buffer implements StreamInterface
|
|||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the byte length of the buffer, or null after detach.
|
||||
*/
|
||||
public function getSize(): ?int
|
||||
{
|
||||
return $this->detached ? null : $this->length();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the total byte length of the buffer.
|
||||
*/
|
||||
public function length(): int
|
||||
{
|
||||
return \strlen($this->data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of bytes between the current cursor position and the end.
|
||||
*/
|
||||
public function remaining() : int
|
||||
{
|
||||
if ($this->detached) {
|
||||
|
|
@ -56,11 +77,19 @@ class Buffer implements StreamInterface
|
|||
return $this->length() - $this->position;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true when the cursor is at or past the end of the buffer.
|
||||
*/
|
||||
public function eof(): bool
|
||||
{
|
||||
return $this->remaining() === 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current byte offset of the cursor.
|
||||
*
|
||||
* @throws \RuntimeException if the stream is detached
|
||||
*/
|
||||
public function tell() : int
|
||||
{
|
||||
$this->ensureAttached();
|
||||
|
|
@ -68,11 +97,22 @@ class Buffer implements StreamInterface
|
|||
return $this->position;
|
||||
}
|
||||
|
||||
/**
|
||||
* Always returns true — buffers are always seekable.
|
||||
*/
|
||||
public function isSeekable(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Moves the cursor to the given position.
|
||||
*
|
||||
* @param int $whence SEEK_SET, SEEK_CUR, or SEEK_END
|
||||
* @throws \InvalidArgumentException for an unrecognised $whence value
|
||||
* @throws \OutOfBoundsException if the resolved position is outside [0, length]
|
||||
* @throws \RuntimeException if the stream is detached
|
||||
*/
|
||||
public function seek(int $offset, int $whence = SEEK_SET): void
|
||||
{
|
||||
$this->ensureAttached();
|
||||
|
|
@ -91,16 +131,30 @@ class Buffer implements StreamInterface
|
|||
$this->position = $position;
|
||||
}
|
||||
|
||||
/**
|
||||
* Moves the cursor to the start of the buffer.
|
||||
*/
|
||||
public function rewind(): void
|
||||
{
|
||||
$this->seek(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Always returns true — buffers are always readable.
|
||||
*/
|
||||
public function isReadable(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads up to $length bytes from the current cursor position.
|
||||
*
|
||||
* Returns fewer bytes than requested when the end of the buffer is reached.
|
||||
*
|
||||
* @throws \InvalidArgumentException if $length is negative
|
||||
* @throws \RuntimeException if the stream is detached
|
||||
*/
|
||||
public function read(int $length): string
|
||||
{
|
||||
$this->ensureAttached();
|
||||
|
|
@ -119,16 +173,30 @@ class Buffer implements StreamInterface
|
|||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Always returns true — buffers are always writable.
|
||||
*/
|
||||
public function isWritable(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Alias for {@see isWritable()}.
|
||||
*/
|
||||
public function isWriteable(): bool
|
||||
{
|
||||
return $this->isWritable();
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes $string at the current cursor position, overwriting existing bytes.
|
||||
*
|
||||
* If the write extends past the end of the buffer, the buffer grows accordingly.
|
||||
* Returns the number of bytes written.
|
||||
*
|
||||
* @throws \RuntimeException if the stream is detached
|
||||
*/
|
||||
public function write(string $string): int
|
||||
{
|
||||
$this->ensureAttached();
|
||||
|
|
@ -151,6 +219,11 @@ class Buffer implements StreamInterface
|
|||
return $length;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all bytes from the current cursor position to the end and advances the cursor.
|
||||
*
|
||||
* @throws \RuntimeException if the stream is detached
|
||||
*/
|
||||
public function getContents(): string
|
||||
{
|
||||
$this->ensureAttached();
|
||||
|
|
@ -165,6 +238,11 @@ class Buffer implements StreamInterface
|
|||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns stream metadata, or a single key if $key is provided.
|
||||
*
|
||||
* Returns null for unknown keys.
|
||||
*/
|
||||
public function getMetadata(?string $key = null): mixed
|
||||
{
|
||||
$metadata = [
|
||||
|
|
@ -177,6 +255,9 @@ class Buffer implements StreamInterface
|
|||
return $key !== null ? $metadata[$key] ?? null : $metadata;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the full buffer contents regardless of cursor position. Returns '' after detach.
|
||||
*/
|
||||
public function __toString(): string
|
||||
{
|
||||
if ($this->detached) {
|
||||
|
|
@ -186,6 +267,9 @@ class Buffer implements StreamInterface
|
|||
return $this->data;
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws \RuntimeException
|
||||
*/
|
||||
private function ensureAttached(): void
|
||||
{
|
||||
if ($this->detached) {
|
||||
|
|
|
|||
|
|
@ -5,6 +5,9 @@ declare(strict_types=1);
|
|||
|
||||
namespace Shufflingpixels\IO\Exception;
|
||||
|
||||
/**
|
||||
* Thrown when a read is attempted past the end of a stream.
|
||||
*/
|
||||
class EndOfStreamException extends IOException
|
||||
{
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,6 +5,9 @@ declare(strict_types=1);
|
|||
|
||||
namespace Shufflingpixels\IO\Exception;
|
||||
|
||||
/**
|
||||
* Thrown when a stream or file operation fails.
|
||||
*/
|
||||
class IOException extends \Exception
|
||||
{
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,10 +7,13 @@ namespace Shufflingpixels\IO;
|
|||
|
||||
use Shufflingpixels\IO\Exception\IOException;
|
||||
|
||||
/** A PSR-7 stream backed by a file on disk, opened via {@see FileMode}. */
|
||||
class File extends Resource
|
||||
{
|
||||
/**
|
||||
* @throws IOException
|
||||
* Opens a file and returns a stream for it.
|
||||
*
|
||||
* @throws IOException if the file cannot be opened
|
||||
*/
|
||||
public static function open(string $filename, FileMode $mode = FileMode::RW) : self
|
||||
{
|
||||
|
|
|
|||
|
|
@ -5,23 +5,30 @@ declare(strict_types=1);
|
|||
|
||||
namespace Shufflingpixels\IO;
|
||||
|
||||
/**
|
||||
* File open modes passed to {@see File::open()}.
|
||||
*
|
||||
* READ — read-only, file must exist.
|
||||
* WRITE — write-only, truncates or creates the file.
|
||||
* RW — read/write, file must exist.
|
||||
*/
|
||||
enum FileMode : string
|
||||
{
|
||||
case READ = 'r';
|
||||
case WRITE = 'w';
|
||||
case RW = 'r+';
|
||||
|
||||
public function seekable()
|
||||
public function seekable(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public function readable()
|
||||
public function readable(): bool
|
||||
{
|
||||
return $this === self::READ || $this === self::RW;
|
||||
}
|
||||
|
||||
public function writeable()
|
||||
public function writeable(): bool
|
||||
{
|
||||
return $this === self::WRITE || $this === self::RW;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,11 +12,25 @@ use RuntimeException;
|
|||
use Shufflingpixels\IO\Exception\IOException;
|
||||
|
||||
|
||||
/**
|
||||
* A read-only PSR-7 stream window over a slice of another seekable stream.
|
||||
*
|
||||
* Presents the bytes [$start, $start + $length) of the underlying stream as an
|
||||
* independent stream with its own cursor starting at offset 0. The underlying
|
||||
* stream is seeked on every read, so LimitedResource instances over the same
|
||||
* base stream can be used independently without interfering with each other.
|
||||
*/
|
||||
class LimitedResource implements StreamInterface
|
||||
{
|
||||
private int $position = 0;
|
||||
private bool $detached = false;
|
||||
|
||||
/**
|
||||
* @param StreamInterface $stream The underlying stream to read from. Must be seekable.
|
||||
* @param int $start Byte offset in $stream where this window begins.
|
||||
* @param int $length Number of bytes this window exposes.
|
||||
* @throws IOException if $stream is not seekable, or $start or $length are negative
|
||||
*/
|
||||
public function __construct(
|
||||
private StreamInterface $stream,
|
||||
private int $start,
|
||||
|
|
@ -33,11 +47,17 @@ class LimitedResource implements StreamInterface
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Detaches from the underlying stream. Does not close it.
|
||||
*/
|
||||
public function close(): void
|
||||
{
|
||||
$this->detach();
|
||||
}
|
||||
|
||||
/**
|
||||
* Detaches from the underlying stream and returns null. Does not close it.
|
||||
*/
|
||||
public function detach(): mixed
|
||||
{
|
||||
if ($this->detached) {
|
||||
|
|
@ -50,16 +70,27 @@ class LimitedResource implements StreamInterface
|
|||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the window length in bytes, or null after detach.
|
||||
*/
|
||||
public function getSize(): ?int
|
||||
{
|
||||
return $this->detached ? null : $this->length;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true when the cursor is at or past the end of the window, or after detach.
|
||||
*/
|
||||
public function eof(): bool
|
||||
{
|
||||
return $this->detached || $this->position >= $this->length;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current byte offset within the window (not the underlying stream).
|
||||
*
|
||||
* @throws \RuntimeException if the stream is detached
|
||||
*/
|
||||
public function tell(): int
|
||||
{
|
||||
$this->ensureAttached();
|
||||
|
|
@ -67,11 +98,22 @@ class LimitedResource implements StreamInterface
|
|||
return $this->position;
|
||||
}
|
||||
|
||||
/**
|
||||
* Always returns true — the window is always seekable.
|
||||
*/
|
||||
public function isSeekable(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Moves the cursor to the given position within the window.
|
||||
*
|
||||
* @param int $whence SEEK_SET, SEEK_CUR, or SEEK_END (relative to the window, not the underlying stream)
|
||||
* @throws \InvalidArgumentException for an unrecognised $whence value
|
||||
* @throws \OutOfBoundsException if the resolved position is outside [0, length]
|
||||
* @throws \RuntimeException if the stream is detached
|
||||
*/
|
||||
public function seek(int $offset, int $whence = SEEK_SET): void
|
||||
{
|
||||
$this->ensureAttached();
|
||||
|
|
@ -90,16 +132,31 @@ class LimitedResource implements StreamInterface
|
|||
$this->position = $position;
|
||||
}
|
||||
|
||||
/**
|
||||
* Moves the cursor to the start of the window.
|
||||
*/
|
||||
public function rewind(): void
|
||||
{
|
||||
$this->seek(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Always returns true — the window is always readable.
|
||||
*/
|
||||
public function isReadable(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads up to $length bytes from the current cursor position within the window.
|
||||
*
|
||||
* Clamps the read to the window boundary so it never reads into adjacent data.
|
||||
* Returns fewer bytes than requested when the end of the window is reached.
|
||||
*
|
||||
* @throws \InvalidArgumentException if $length is negative
|
||||
* @throws \RuntimeException if the stream is detached
|
||||
*/
|
||||
public function read(int $length): string
|
||||
{
|
||||
$this->ensureAttached();
|
||||
|
|
@ -120,16 +177,27 @@ class LimitedResource implements StreamInterface
|
|||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Always returns false — writing to a window is not supported.
|
||||
*/
|
||||
public function isWritable(): bool
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws IOException always — the window is read-only
|
||||
*/
|
||||
public function write(string $string): int
|
||||
{
|
||||
throw new IOException('LimitedResource is read-only');
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all bytes from the current cursor position to the end of the window.
|
||||
*
|
||||
* @throws \RuntimeException if the stream is detached
|
||||
*/
|
||||
public function getContents(): string
|
||||
{
|
||||
$this->ensureAttached();
|
||||
|
|
@ -141,11 +209,19 @@ class LimitedResource implements StreamInterface
|
|||
return $this->read($this->length - $this->position);
|
||||
}
|
||||
|
||||
/**
|
||||
* Always returns null — no metadata is available for a window stream.
|
||||
*/
|
||||
public function getMetadata(?string $key = null): mixed
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the full window contents regardless of current cursor position.
|
||||
*
|
||||
* Returns '' after detach or if an error occurs during reading.
|
||||
*/
|
||||
public function __toString(): string
|
||||
{
|
||||
if ($this->detached) {
|
||||
|
|
@ -160,6 +236,9 @@ class LimitedResource implements StreamInterface
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws \RuntimeException
|
||||
*/
|
||||
private function ensureAttached(): void
|
||||
{
|
||||
if ($this->detached) {
|
||||
|
|
|
|||
|
|
@ -8,6 +8,12 @@ namespace Shufflingpixels\IO;
|
|||
use Psr\Http\Message\StreamInterface;
|
||||
use Shufflingpixels\IO\Exception\IOException;
|
||||
|
||||
/**
|
||||
* Base PSR-7 stream implementation wrapping a PHP file resource.
|
||||
*
|
||||
* Concrete subclasses supply the resource and declare which capabilities
|
||||
* (seekable, readable, writable) are available for their use-case.
|
||||
*/
|
||||
abstract class Resource implements StreamInterface
|
||||
{
|
||||
protected ?int $size = null;
|
||||
|
|
@ -23,6 +29,9 @@ abstract class Resource implements StreamInterface
|
|||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes the underlying file resource. Subsequent calls are no-ops.
|
||||
*/
|
||||
public function close(): void
|
||||
{
|
||||
if ($this->resource !== null) {
|
||||
|
|
@ -31,6 +40,9 @@ abstract class Resource implements StreamInterface
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Detaches and returns the underlying file resource, leaving the stream unusable.
|
||||
*/
|
||||
public function detach(): mixed
|
||||
{
|
||||
$resource = $this->resource;
|
||||
|
|
@ -39,6 +51,11 @@ abstract class Resource implements StreamInterface
|
|||
return $resource;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the byte size of the stream, or null for non-seekable streams.
|
||||
*
|
||||
* The result is cached after the first call and invalidated by any write.
|
||||
*/
|
||||
public function getSize(): ?int
|
||||
{
|
||||
if (!$this->isSeekable()) {
|
||||
|
|
@ -56,21 +73,36 @@ abstract class Resource implements StreamInterface
|
|||
return $this->size;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true when the underlying resource is at end-of-file.
|
||||
*/
|
||||
public function eof(): bool
|
||||
{
|
||||
return feof($this->resource);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether this stream supports seeking.
|
||||
*/
|
||||
public function isSeekable(): bool
|
||||
{
|
||||
return $this->seekable;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current byte offset of the file cursor.
|
||||
*/
|
||||
public function tell() : int
|
||||
{
|
||||
return ftell($this->resource);
|
||||
}
|
||||
|
||||
/**
|
||||
* Moves the file cursor to the given position.
|
||||
*
|
||||
* @param int $whence SEEK_SET, SEEK_CUR, or SEEK_END
|
||||
* @throws IOException if the stream is not seekable or the seek fails
|
||||
*/
|
||||
public function seek(int $position, int $whence = SEEK_SET): void
|
||||
{
|
||||
if (!$this->isSeekable()) {
|
||||
|
|
@ -82,26 +114,45 @@ abstract class Resource implements StreamInterface
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether this stream supports reading.
|
||||
*/
|
||||
public function isReadable(): bool
|
||||
{
|
||||
return $this->readable;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads up to $length bytes from the current cursor position.
|
||||
*/
|
||||
public function read(int $length): string
|
||||
{
|
||||
return fread($this->resource, $length);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether this stream supports writing.
|
||||
*/
|
||||
public function isWritable(): bool
|
||||
{
|
||||
return $this->writeable;
|
||||
}
|
||||
|
||||
/**
|
||||
* Alias for {@see isWritable()}.
|
||||
*/
|
||||
public function isWriteable(): bool
|
||||
{
|
||||
return $this->isWritable();
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes $string at the current cursor position and returns the bytes written.
|
||||
*
|
||||
* Invalidates the cached size so {@see getSize()} reflects the new length.
|
||||
*
|
||||
* @throws IOException if the stream is not writable or the write fails
|
||||
*/
|
||||
public function write(string $string): int
|
||||
{
|
||||
if (!$this->writeable) {
|
||||
|
|
@ -117,16 +168,27 @@ abstract class Resource implements StreamInterface
|
|||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Moves the cursor to the start of the stream.
|
||||
*/
|
||||
public function rewind(): void
|
||||
{
|
||||
$this->seek(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all bytes from the current cursor position to the end.
|
||||
*/
|
||||
public function getContents(): string
|
||||
{
|
||||
return (string) stream_get_contents($this->resource);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns stream metadata, or a single key if $key is provided.
|
||||
*
|
||||
* Returns null for unknown keys.
|
||||
*/
|
||||
public function getMetadata(?string $key = null): mixed
|
||||
{
|
||||
$data = stream_get_meta_data($this->resource);
|
||||
|
|
@ -134,6 +196,9 @@ abstract class Resource implements StreamInterface
|
|||
return $key !== null ? $data[$key] ?? null : $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all remaining stream contents as a string.
|
||||
*/
|
||||
public function __toString(): string
|
||||
{
|
||||
return $this->getContents();
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue