Method Overview
Searching the Internet, I came across various solutions. I can group them in three approaches:
- naive who use the
file() function of PHP; - cheating that run the
tail command on the system; - powerful ones who happily jump around an open file using
fseek() .
In the end, I decided (or wrote) five decisions, naive, fraudulent and three powerful.
- The most concise naive solution using the built-in array functions.
- only a solution based on the
tail command is possible , which has a slightly bigger problem: it does not start if tail unavailable, since it is non-Unix (Windows) or in limited environments that do not allow the system functions. - A solution in which single bytes are read from the end of a file search for (and counting) newline characters is found here .
- a multibyte buffered solution optimized for large files is found here .
- A slightly modified version of solution # 4 , in which the buffer length is dynamic, is determined according to the number of lines to extract.
All solutions work . In the sense that they return the expected result from any file and any number of lines that we request (with the exception of solution # 1, which can break PHP's memory limits in case of large files without returning anything). But which is better?
Performance tests
To answer the question, I run tests. How is this done, right?
I prepared a sample of a 100 KB file combining the different files found in my /var/log . Then I wrote a PHP script that uses each of the five solutions to extract 1, 2, ..., 10, 20, ... 100, 200, ..., 1000 lines from the end of the file. Each individual test is repeated ten times (this is something like 5 ร 28 ร 10 = 1400 tests), measuring the average elapsed time in microseconds.
I run the script on my local development machine (Xubuntu 12.04, PHP 5.3.10, dual-core processor 2.70 GHz, RAM 2 GB) using the command line PHP translator. Here are the results:

Decision number 1 and number 2 looks worse. Solution # 3 is only useful when we need to read a few lines. Decisions No. 4 and No. 5 seem to be the best. Notice how dynamic buffer size can optimize the algorithm: the execution time is slightly shorter for a few lines due to the reduced buffer.
Try with a large file. What if we need to read a 10 MB log file?

Now solution # 1 is definitely worse: in fact, loading just 10 MB of a file into memory is not a great idea. I run tests also on a 1MB and 100MB file, and this is almost the same situation.
And for tiny log files? What is the graph for a 10KB file:

Solution No. 1 is now the best! Loading 10K into memory is not a big problem for PHP. Also # 4 and # 5 work well. However, this is an edge case: a 10K log means something like 150/200 lines ...
You can download all my test files, sources and results here .
Final thoughts
Solution # 5 is highly recommended for general use: works great with every file size and is especially good at reading multiple lines.
Avoid solution # 1 if you must read files larger than 10 KB.
Solution # 2 and # 3 are not the best for every test I run: # 2 never works less than 2ms and # 3 depends heavily on the number of lines you specify (works just fine with only 1 or 2 lines) .