Chad Minick
Software Developer

Reading Files in Reverse in PHP

August 12, 2010 Comments,

Every once in awhile I get someone asking me in ##php on freenode how to prepend entries to a file. Usually it's because it's a log file of some sort and they want to read the latest entries first. This is usually for someone (not necessarily the developer) who isn't used to the whole habit of reading log files from the bottom up. Years of using tail to read log files has lead me to have different expectations than a typical Joe (not you, Chance), and wanting new entries first is a reasonable request. However, one has to remember that viewing data and storing data are two completely different things. The most efficient way to view data isn't always the most efficient way to store it.

Prepending data to a file isn't that easy, hence, why log files traditionally are appended to. There really is no safe way to do it in PHP. I kind of doubt there are safe ways to do it in any language that will handle concurrent operations well. It's far easier to append data to your file, then read it backwards. I've seen some pretty awful solutions to this problem in PHP, mostly involving reading the whole file in memory using file() and then calling array_reverse() on the result. Fortunately, PHP's SPL makes reading a file in reverse pretty easy without loading the whole file into memory at once. Here is the class:

<?php
class ReverseFileReader extends SplFileObject {
  private $lineCount;
  private $loc;
  public function __construct($filename, $use_include_path=true) {
        parent::__construct($filename, 'r', $use_include_path);
        $this->lineCount = 0;
        while(!$this->eof()) {
          $this->seek($this->lineCount);
          ++$this->lineCount;
        }
        $this->rewind();
  }

  public function next() {
    $this->loc--;
    if(!$this->valid()) {
        return;
    }
    $this->seek($this->loc);
  }

  public function key() {
     return $this->loc;
  }

  public function valid() {
    return !($this->loc < 0 || $this->loc > $this->lineCount);
  }

  public function rewind() {
     $this->seek($this->lineCount);
     $this->loc = $this->lineCount;
  }
}

Once you have this class in your toolbox, using it is pretty easy:

$file = new ReverseFileReader('somefile.log');
foreach($file as $line) {
  echo $line;
}
comments powered by Disqus
Chad Minick | Software Developer
Twitter | Github | LinkedIn