134 lines
3.4 KiB
PHP
134 lines
3.4 KiB
PHP
|
<?php
|
||
|
// GenericStream.php
|
||
|
// Created: 2021-04-30
|
||
|
// Updated: 2022-02-27
|
||
|
|
||
|
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 {
|
||
|
fclose($this->stream);
|
||
|
}
|
||
|
|
||
|
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);
|
||
|
}
|
||
|
}
|