+ throw new \Exception("Unable to open $filename");
+ }
+ $this->filesize = filesize($filename);
+ $this->pos = -1;
+ $this->buffer = null;
+ $this->key = -1;
+ $this->value = null;
+ return $this;
+ }
+
+ /**
+ * Read $size bytes behind last position
+ *
+ * @param int $size
+ * @return string
+ */
+ private function _read(int $size): string
+ {
+ $this->pos -= $size;
+ fseek($this->fh, $this->pos);
+ return fread($this->fh, $size);
+ }
+
+ /**
+ * Read next line from end of file
+ * Return null if no lines are left to read
+ *
+ * @return string|null Depending on data being buffered
+ */
+ private function _readline(): ?string
+ {
+ $buffer = & $this->buffer;
+ while (true) {
+ if ($this->pos == 0) {
+ return array_pop($buffer);
+ }
+ if (is_null($buffer)) {
+ return null;
+ }
+ if (count($buffer) > 1) {
+ return array_pop($buffer);
+ }
+ $buffer = explode(self::SEPARATOR, $this->_read(self::BUFFER_SIZE) . $buffer[0]);
+ }
+ }
+
+ /**
+ * Fetch next line from end and set it as current iterator value.
+ *
+ * @see Iterator::next()
+ * @return void
+ */
+ #[\ReturnTypeWillChange]
+ public function next()
+ {
+ ++$this->key;
+ $this->value = $this->_readline();
+ }
+
+ /**
+ * Rewind iterator to the first line at the end of file
+ *
+ * @see Iterator::rewind()
+ * @return void
+ */
+ #[\ReturnTypeWillChange]
+ public function rewind()
+ {
+ if ($this->filesize > 0) {
+ $this->pos = $this->filesize;
+ $this->value = null;
+ $this->key = -1;
+ $this->buffer = explode(self::SEPARATOR, $this->_read($this->filesize % self::BUFFER_SIZE ?: self::BUFFER_SIZE));
+ $this->next();