file = $file; $this->pos = $this->tell(); } private function tell(): int { return ftell( $this->file ); } public function pos(): int { return $this->pos; } /** * Seek to given absolute file position. * @throws Exception on error */ public function seek( int $pos ): void { $this->pos = intval( $pos ); if ( $this->pos === $this->tell() ) { return; } $retval = fseek( $this->file, $this->pos, SEEK_SET ); if ( $retval < 0 ) { throw new Exception( "Failed to seek to $this->pos bytes" ); } } /** * Read $len bytes or return null on EOF/short read. * @throws Exception on error */ public function read( int $len ): ?string { $this->seek( $this->pos ); $bytes = fread( $this->file, $len ); if ( $bytes === false ) { throw new Exception( "Read error for $len bytes at $this->pos" ); } if ( strlen( $bytes ) < $len ) { return null; } $this->pos += strlen( $bytes ); return $bytes; } /** * Read exactly $len bytes * @throws Exception on error or short read */ public function readExactly( int $len ): string { $bytes = $this->read( $len ); if ( $bytes === null ) { throw new Exception( "Short read for $len bytes at $this->pos" ); } return $bytes; } /** * Write the given data and return number of bytes written. * Short writes are possible on network connections, in theory. * @throws Exception on error */ public function write( string $bytes ): int { $this->seek( $this->pos ); $len = strlen( $bytes ); $nbytes = fwrite( $this->file, $bytes ); if ( $nbytes === false ) { throw new Exception( "Write error for $len bytes at $this->pos" ); } return $nbytes; } }