| | |
| | class BufferedReader implements StreamReader { |
| | - private mixed $handle; |
| | - private bool $temporary; |
| | - private bool $owned; |
| | + private const CHUNK_SIZE = 65536; |
| | |
| | - private function __construct( mixed $handle, bool $temporary, bool $owned = true ) { |
| | + private mixed $handle; |
| | + private bool $temporary; |
| | + private bool $owned; |
| | + private string $buffer; |
| | + private int $bufferPos; |
| | + private int $bufferLen; |
| | + |
| | + private function __construct( |
| | + mixed $handle, |
| | + bool $temporary, |
| | + bool $owned = true |
| | + ) { |
| | $this->handle = $handle; |
| | $this->temporary = $temporary; |
| | $this->owned = $owned; |
| | + $this->buffer = ''; |
| | + $this->bufferPos = 0; |
| | + $this->bufferLen = 0; |
| | } |
| | |
| | public static function open( string $path ): self { |
| | return new self( @fopen( $path, 'rb' ), false ); |
| | } |
| | |
| | public static function createTemp(): self { |
| | - return new self( @tmpfile(), true ); |
| | + return new self( @fopen( 'php://temp/maxmemory:8388608', 'w+b' ), true ); |
| | } |
| | |
 |
| | |
| | public function __destruct() { |
| | - $this->owned && $this->isOpen() ? fclose( $this->handle ) : null; |
| | + if( $this->owned && $this->isOpen() ) { |
| | + fclose( $this->handle ); |
| | + } |
| | } |
| | |
| | public function read( int $length ): string { |
| | - return $this->isOpen() && ! $this->eof() |
| | - ? (string)fread( $this->handle, $length ) |
| | - : ''; |
| | + $result = ''; |
| | + |
| | + if( $this->isOpen() ) { |
| | + $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 ); |
| | + |
| | + $this->buffer .= $chunk; |
| | + $this->bufferLen += strlen( $chunk ); |
| | + } |
| | + |
| | + $result = substr( $this->buffer, $this->bufferPos, $length ); |
| | + |
| | + $this->bufferPos += strlen( $result ); |
| | + |
| | + 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; |
| | + } |
| | + } |
| | + |
| | + return $result; |
| | } |
| | |
| | public function write( string $data ): bool { |
| | - return $this->temporary && $this->isOpen() |
| | - ? fwrite( $this->handle, $data ) !== false |
| | - : false; |
| | + $result = false; |
| | + |
| | + if( $this->temporary && $this->isOpen() ) { |
| | + $this->buffer = ''; |
| | + $this->bufferPos = 0; |
| | + $this->bufferLen = 0; |
| | + |
| | + $result = fwrite( $this->handle, $data ) !== false; |
| | + } |
| | + |
| | + return $result; |
| | } |
| | |
| | public function seek( int $offset, int $whence = SEEK_SET ): bool { |
| | - return $this->isOpen() |
| | - ? fseek( $this->handle, $offset, $whence ) === 0 |
| | - : false; |
| | + $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; |
| | + |
| | + if( $result ) { |
| | + $this->buffer = ''; |
| | + $this->bufferPos = 0; |
| | + $this->bufferLen = 0; |
| | + } |
| | + } |
| | + |
| | + return $result; |
| | } |
| | |
| | public function tell(): int { |
| | return $this->isOpen() |
| | - ? (int)ftell( $this->handle ) |
| | + ? (int)ftell( $this->handle ) - ($this->bufferLen - $this->bufferPos) |
| | : 0; |
| | } |
| | |
| | public function eof(): bool { |
| | return $this->isOpen() |
| | - ? feof( $this->handle ) |
| | + ? feof( $this->handle ) && $this->bufferPos >= $this->bufferLen |
| | : true; |
| | } |
| | |
| | public function rewind(): void { |
| | - $this->isOpen() ? rewind( $this->handle ) : null; |
| | + if( $this->isOpen() ) { |
| | + rewind( $this->handle ); |
| | + |
| | + $this->buffer = ''; |
| | + $this->bufferPos = 0; |
| | + $this->bufferLen = 0; |
| | + } |
| | } |
| | } |