| | |
| | class BufferedReader implements StreamReader { |
| | - private const CHUNK_SIZE = 65536; |
| | + private const int CHUNK_SIZE = 65536; |
| | |
| | private mixed $handle; |
| | - private bool $temporary; |
| | - private string $buffer; |
| | - private int $bufferPos; |
| | - private int $bufferLen; |
| | - |
| | - private function __construct( mixed $handle, bool $temporary ) { |
| | - $this->handle = $handle; |
| | - $this->temporary = $temporary; |
| | - $this->buffer = ''; |
| | - $this->bufferPos = 0; |
| | - $this->bufferLen = 0; |
| | - } |
| | - |
| | - public static function open( string $path ): self { |
| | - return new self( @fopen( $path, 'rb' ), false ); |
| | - } |
| | + private string $buffer = ''; |
| | + private int $bufferPos = 0; |
| | + private int $bufferLen = 0; |
| | |
| | - public static function createTemp(): self { |
| | - return new self( @fopen( 'php://temp/maxmemory:65536', 'w+b' ), true ); |
| | - } |
| | + private readonly bool $writable; |
| | |
| | - public function isOpen(): bool { |
| | - return is_resource( $this->handle ); |
| | + public function __construct( string $path, string $mode = 'rb' ) { |
| | + $this->handle = @\fopen( $path, $mode ); |
| | + $this->writable = $mode !== 'rb'; |
| | } |
| | |
| | public function __destruct() { |
| | - if( $this->isOpen() ) { |
| | - fclose( $this->handle ); |
| | + if( $this->handle !== false ) { |
| | + \fclose( $this->handle ); |
| | + |
| | + $this->handle = false; |
| | } |
| | } |
| | |
| | public function read( int $length ): string { |
| | - $result = ''; |
| | - |
| | - if( $this->isOpen() ) { |
| | - $available = $this->bufferLen - $this->bufferPos; |
| | + $available = $this->bufferLen - $this->bufferPos; |
| | |
| | - if( $available < $length && !feof( $this->handle ) ) { |
| | - $need = $length - $available; |
| | - $fetch = $need > self::CHUNK_SIZE ? $need : self::CHUNK_SIZE; |
| | - $chunk = (string)fread( $this->handle, $fetch ); |
| | + if( $available < $length |
| | + && $this->handle !== false |
| | + && !\feof( $this->handle ) |
| | + ) { |
| | + $chunk = \fread( |
| | + $this->handle, |
| | + \max( $length - $available, self::CHUNK_SIZE ) |
| | + ); |
| | |
| | + if( $chunk !== false && $chunk !== '' ) { |
| | $this->buffer .= $chunk; |
| | - $this->bufferLen += strlen( $chunk ); |
| | + $this->bufferLen += \strlen( $chunk ); |
| | + $available = $this->bufferLen - $this->bufferPos; |
| | } |
| | + } |
| | |
| | - $result = substr( $this->buffer, $this->bufferPos, $length ); |
| | + $take = \min( $available, $length ); |
| | |
| | - $this->bufferPos += strlen( $result ); |
| | + $result = $take > 0 |
| | + ? \substr( $this->buffer, $this->bufferPos, $take ) |
| | + : ''; |
| | + |
| | + if( $take > 0 ) { |
| | + $this->bufferPos += $take; |
| | |
| | if( $this->bufferPos >= $this->bufferLen ) { |
| | $this->buffer = ''; |
| | $this->bufferPos = 0; |
| | $this->bufferLen = 0; |
| | - } elseif( $this->bufferPos > self::CHUNK_SIZE ) { |
| | - $this->buffer = substr( $this->buffer, $this->bufferPos ); |
| | - $this->bufferLen = strlen( $this->buffer ); |
| | - $this->bufferPos = 0; |
| | + } elseif( $this->bufferPos >= self::CHUNK_SIZE ) { |
| | + $this->buffer = \substr( $this->buffer, $this->bufferPos ); |
| | + $this->bufferLen -= $this->bufferPos; |
| | + $this->bufferPos = 0; |
| | } |
| | } |
| | |
| | return $result; |
| | } |
| | |
| | public function write( string $data ): bool { |
| | - $result = false; |
| | + $canWrite = $this->writable && $this->handle !== false; |
| | |
| | - if( $this->temporary && $this->isOpen() ) { |
| | + if( $canWrite ) { |
| | $this->buffer = ''; |
| | $this->bufferPos = 0; |
| | $this->bufferLen = 0; |
| | - |
| | - $result = fwrite( $this->handle, $data ) !== false; |
| | } |
| | |
| | - return $result; |
| | + return $canWrite ? \fwrite( $this->handle, $data ) !== false : false; |
| | } |
| | |
| | public function seek( int $offset, int $whence = SEEK_SET ): bool { |
| | - $result = false; |
| | - |
| | - if( $this->isOpen() ) { |
| | - if( $whence === SEEK_CUR ) { |
| | - $current = (int)ftell( $this->handle ); |
| | - $logical = $current - ($this->bufferLen - $this->bufferPos); |
| | - $offset = $logical + $offset; |
| | - $whence = SEEK_SET; |
| | - } |
| | - |
| | - $result = fseek( $this->handle, $offset, $whence ) === 0; |
| | + $success = $this->handle !== false && \fseek( |
| | + $this->handle, |
| | + $whence === SEEK_CUR |
| | + ? (int)\ftell( $this->handle ) |
| | + - $this->bufferLen |
| | + + $this->bufferPos |
| | + + $offset |
| | + : $offset, |
| | + $whence === SEEK_CUR ? SEEK_SET : $whence |
| | + ) === 0; |
| | |
| | - if( $result ) { |
| | - $this->buffer = ''; |
| | - $this->bufferPos = 0; |
| | - $this->bufferLen = 0; |
| | - } |
| | + if( $success ) { |
| | + $this->buffer = ''; |
| | + $this->bufferPos = 0; |
| | + $this->bufferLen = 0; |
| | } |
| | |
| | - return $result; |
| | + return $success; |
| | } |
| | |
| | public function tell(): int { |
| | - return $this->isOpen() |
| | - ? (int)ftell( $this->handle ) - ($this->bufferLen - $this->bufferPos) |
| | + return $this->handle !== false |
| | + ? (int)\ftell( $this->handle ) - $this->bufferLen + $this->bufferPos |
| | : 0; |
| | } |
| | |
| | public function eof(): bool { |
| | - return $this->isOpen() |
| | - ? feof( $this->handle ) && $this->bufferPos >= $this->bufferLen |
| | - : true; |
| | + return $this->bufferPos >= $this->bufferLen |
| | + && ($this->handle === false || \feof( $this->handle )); |
| | } |
| | |
| | public function rewind(): void { |
| | - if( $this->isOpen() ) { |
| | - rewind( $this->handle ); |
| | + if( $this->handle !== false ) { |
| | + \rewind( $this->handle ); |
| | |
| | $this->buffer = ''; |