index/src/IO/GenericStream.php

136 lines
3.5 KiB
PHP

<?php
// GenericStream.php
// Created: 2021-04-30
// Updated: 2023-07-17
namespace Index\IO;
use InvalidArgumentException;
class GenericStream extends Stream {
public const READABLE = [
'r', 'rb', 'r+', 'r+b', 'w+', 'w+b',
'a+', 'a+b', 'x+', 'x+b', 'c+', 'c+b',
];
public const WRITEABLE = [
'r+', 'r+b', 'w', 'wb', 'w+', 'w+b',
'a', 'ab', 'a+', 'a+b', 'x', 'xb',
'x+', 'x+b', 'c', 'cb', 'c+', 'c+b',
];
protected $stream; // resource
private bool $canRead;
private bool $canWrite;
private bool $canSeek;
public function __construct($stream) {
if(!is_resource($stream) || get_resource_type($stream) !== 'stream')
throw new InvalidArgumentException('$stream is not a valid stream resource.');
$this->stream = $stream;
$metaData = $this->metaData();
$this->canRead = in_array($metaData['mode'], self::READABLE);
$this->canWrite = in_array($metaData['mode'], self::WRITEABLE);
$this->canSeek = $metaData['seekable'];
}
public function getResource() {
return $this->stream;
}
private function stat(): array {
return fstat($this->stream);
}
public function getPosition(): int {
return ftell($this->stream);
}
public function getLength(): int {
if(!$this->canSeek())
return -1;
return $this->stat()['size'];
}
public function setLength(int $length): void {
ftruncate($this->stream, $length);
}
private function metaData(): array {
return stream_get_meta_data($this->stream);
}
public function canRead(): bool {
return $this->canRead;
}
public function canWrite(): bool {
return $this->canWrite;
}
public function canSeek(): bool {
return $this->canSeek;
}
public function hasTimedOut(): bool {
return $this->metaData()['timed_out'];
}
public function isBlocking(): bool {
return $this->metaData()['blocked'];
}
public function isEnded(): bool {
return feof($this->stream);
}
public function readChar(): ?string {
$char = fgetc($this->stream);
if($char === false)
return null;
return $char;
}
public function readLine(): ?string {
return ($line = fgets($this->stream)) === false
? null : $line;
}
public function read(int $length): ?string {
$buffer = fread($this->stream, $length);
if($buffer === false)
return null;
return $buffer;
}
public function seek(int $offset, int $origin = Stream::START): int {
return fseek($this->stream, $offset, $origin);
}
public function write(string $buffer, int $length = -1): void {
if($length >= 0)
fwrite($this->stream, $buffer, $length);
else
fwrite($this->stream, $buffer);
}
public function writeChar(string $char): void {
fwrite($this->stream, $char, 1);
}
public function flush(): void {
fflush($this->stream);
}
public function close(): void {
try {
fclose($this->stream);
} catch(\Error $ex) {}
}
public function copyTo(Stream $other): void {
if($other instanceof GenericStream) {
stream_copy_to_stream($this->stream, $other->stream);
} else parent::copyTo($other);
}
public function __toString(): string {
return stream_get_contents($this->stream);
}
}