file_get_contents() returns a file as a string (optionally a byte range) and file() as an array of lines. For logs and exports, read a line at a time with fgets() so memory stays flat; fread() reads bytes, and fseek() and ftell() move and report the position.
<?php
declare(strict_types=1);
$fp = fopen($log = __DIR__ . '/access.log', 'w');
for ($i = 1; $i <= 200_000; $i++) {
fprintf($fp, "%06d GET /books/%03d %d\n", $i, $i % 500, $i % 7 ? 200 : 404);
}
fclose($fp);
echo filesize($log), ' bytes | ', file_get_contents($log, false, null, 26, 26); // line 2
$fp = fopen($log, 'r');
$base = memory_get_usage();
for ($errors = 0; ($line = fgets($fp)) !== false;) $errors += (int) str_ends_with($line, " 404\n");
printf("fgets: %d errors, peak +%.1f MB\n", $errors, (memory_get_peak_usage() - $base) / 1e6);
$lines = file($log, FILE_IGNORE_NEW_LINES);
printf("file(): %d lines, peak +%.1f MB\n", count($lines), (memory_get_peak_usage() - $base) / 1e6);Output
5200000 bytes | 000002 GET /books/002 200 fgets: 28571 errors, peak +0.0 MB file(): 200000 lines, peak +20.6 MB
file() holds 200,000 strings, four times the file's size; fgets() holds one line. Keep the !== false: a final 0 with no newline is falsy. readfile() streams a file to the output, and fgetss() was removed in PHP 8.0 (use strip_tags()).