stream = $stream; $this->closeStream = $closeStream; $this->reset(); } /** * The destructor. */ public function __destruct() { $this->cleanUp(); } /** * Closes the file handle. */ public function cleanUp() { if ($this->closeStream && is_resource($this->stream)) { \fclose($this->stream); } } /** * Returns the byte length of the buffer. * * @param bool $atOffset * @return int */ public function getBufferLength($atOffset = false) { if ($atOffset === false) { return $this->bufferLength; } return $this->bufferLength - $this->offset; } /** * Get the current position in the stream. * * @return int */ public function getPosition() { return $this->position; } /** * Returns the current buffer. * * @param bool $atOffset * @return string */ public function getBuffer($atOffset = true) { if ($atOffset === false) { return $this->buffer; } $string = \substr($this->buffer, $this->offset); return (string) $string; } /** * Gets a byte at a specific position in the buffer. * * If the position is invalid the method will return false. * * If the $position parameter is set to null the value of $this->offset will be used. * * @param int|null $position * @return string|bool */ public function getByte($position = null) { $position = (int) ($position !== null ? $position : $this->offset); if ($position >= $this->bufferLength && (!$this->increaseLength() || $position >= $this->bufferLength) ) { return false; } return $this->buffer[$position]; } /** * Returns a byte at a specific position, and set the offset to the next byte position. * * If the position is invalid the method will return false. * * If the $position parameter is set to null the value of $this->offset will be used. * * @param int|null $position * @return string|bool */ public function readByte($position = null) { if ($position !== null) { $position = (int) $position; // check if needed bytes are available in the current buffer if (!($position >= $this->position && $position < $this->position + $this->bufferLength)) { $this->reset($position); $offset = $this->offset; } else { $offset = $position - $this->position; } } else { $offset = $this->offset; } if ($offset >= $this->bufferLength && ((!$this->increaseLength()) || $offset >= $this->bufferLength) ) { return false; } $this->offset = $offset + 1; return $this->buffer[$offset]; } /** * Read bytes from the current or a specific offset position and set the internal pointer to the next byte. * * If the position is invalid the method will return false. * * If the $position parameter is set to null the value of $this->offset will be used. * * @param int $length * @param int|null $position * @return string */ public function readBytes($length, $position = null) { $length = (int) $length; if ($position !== null) { // check if needed bytes are available in the current buffer if (!($position >= $this->position && $position < $this->position + $this->bufferLength)) { $this->reset($position, $length); $offset = $this->offset; } else { $offset = $position - $this->position; } } else { $offset = $this->offset; } if (($offset + $length) > $this->bufferLength && ((!$this->increaseLength($length)) || ($offset + $length) > $this->bufferLength) ) { return false; } $bytes = \substr($this->buffer, $offset, $length); $this->offset = $offset + $length; return $bytes; } /** * Read a line from the current position. * * @param int $length * @return string|bool */ public function readLine($length = 1024) { if ($this->ensureContent() === false) { return false; } $line = ''; while ($this->ensureContent()) { $char = $this->readByte(); if ($char === "\n") { break; } if ($char === "\r") { if ($this->getByte() === "\n") { $this->addOffset(1); } break; } $line .= $char; if (\strlen($line) >= $length) { break; } } return $line; } /** * Set the offset position in the current buffer. * * @param int $offset */ public function setOffset($offset) { if ($offset > $this->bufferLength || $offset < 0) { throw new \OutOfRangeException( \sprintf('Offset (%s) out of range (length: %s)', $offset, $this->bufferLength) ); } $this->offset = (int) $offset; } /** * Returns the current offset in the current buffer. * * @return int */ public function getOffset() { return $this->offset; } /** * Add an offset to the current offset. * * @param int $offset */ public function addOffset($offset) { $this->setOffset($this->offset + $offset); } /** * Make sure that there is at least one character beyond the current offset in the buffer. * * @return bool */ public function ensureContent() { while ($this->offset >= $this->bufferLength) { if (!$this->increaseLength()) { return false; } } return true; } /** * Returns the stream. * * @return resource */ public function getStream() { return $this->stream; } /** * Gets the total available length. * * @return int */ public function getTotalLength() { if ($this->totalLength === null) { $stat = \fstat($this->stream); $this->totalLength = $stat['size']; } return $this->totalLength; } /** * Resets the buffer to a position and re-read the buffer with the given length. * * If the $pos parameter is negative the start buffer position will be the $pos'th position from * the end of the file. * * If the $pos parameter is negative and the absolute value is bigger then the totalLength of * the file $pos will set to zero. * * @param int|null $pos Start position of the new buffer * @param int $length Length of the new buffer. Mustn't be negative */ public function reset($pos = 0, $length = 200) { if ($pos === null) { $pos = $this->position + $this->offset; } elseif ($pos < 0) { $pos = \max(0, $this->getTotalLength() + $pos); } \fseek($this->stream, $pos); $this->position = $pos; $this->buffer = $length > 0 ? \fread($this->stream, $length) : ''; $this->bufferLength = \strlen($this->buffer); $this->offset = 0; // If a stream wrapper is in use it is possible that // length values > 8096 will be ignored, so use the // increaseLength()-method to correct that behavior if ($this->bufferLength < $length && $this->increaseLength($length - $this->bufferLength)) { // increaseLength parameter is $minLength, so cut to have only the required bytes in the buffer $this->buffer = \substr($this->buffer, 0, $length); $this->bufferLength = \strlen($this->buffer); } } /** * Ensures bytes in the buffer with a specific length and location in the file. * * @param int $pos * @param int $length * @see reset() */ public function ensure($pos, $length) { if ($pos >= $this->position && $pos < ($this->position + $this->bufferLength) && ($this->position + $this->bufferLength) >= ($pos + $length) ) { $this->offset = $pos - $this->position; } else { $this->reset($pos, $length); } } /** * Forcefully read more data into the buffer. * * @param int $minLength * @return bool Returns false if the stream reaches the end */ public function increaseLength($minLength = 100) { $length = \max($minLength, 100); if (\feof($this->stream) || $this->getTotalLength() === $this->position + $this->bufferLength) { return false; } $newLength = $this->bufferLength + $length; do { $this->buffer .= \fread($this->stream, $newLength - $this->bufferLength); $this->bufferLength = \strlen($this->buffer); } while (($this->bufferLength !== $newLength) && !\feof($this->stream)); return true; } }