PHP · TUTORIAL
How to Read a Text File Line by Line in PHP
Read a small list quickly, or stream a larger file without loading the whole thing into memory.
A plain text file is still a useful data source for a small PHP website. It can hold a list of page URLs, daily picks, redirects, labels, or other information that does not need a database. The key questions are whether blank lines matter, whether line numbers matter, and how large the file might grow. The answers determine how you should read it.
This tutorial uses a file named items.txt in the same directory as the PHP script. One item appears on each line. We will first load all lines into an array, then read them one at a time from a file handle. Both examples check for errors instead of silently treating a missing file as an empty list.
Use an absolute path based on the script
A relative path such as items.txt may be resolved relative to a process’s working directory, which can differ between a web request, a command-line run, and an included script. __DIR__ refers to the directory containing the current PHP file. Joining that directory to the filename makes the example predictable even when another page includes the code.
<?php
$path = __DIR__ . '/items.txt';
if (!is_readable($path)) {
throw new RuntimeException('The items file is missing or unreadable.');
}
If the data should not be directly downloadable by visitors, store it outside the public web root and adjust the path. For example, a script under public_html may read from a sibling private directory when your hosting arrangement allows it. Do not rely on a hard-to-guess filename to protect sensitive data.
Load a small file with file()
For a short list, file() returns an array whose elements correspond to physical lines. FILE_IGNORE_NEW_LINES removes the line-ending characters from each element. It does not turn an empty line into a nonexistent line, which is helpful if the line position has meaning.
<?php
$path = __DIR__ . '/items.txt';
$lines = file($path, FILE_IGNORE_NEW_LINES);
if ($lines === false) {
throw new RuntimeException('Could not read items.txt.');
}
foreach ($lines as $number => $line) {
echo ($number + 1) . ': ' . htmlspecialchars($line, ENT_QUOTES, 'UTF-8') . '<br>';
}
The array index starts at zero, while a person usually calls the first line “line 1,” hence $number + 1. When printing a line into HTML, escape it with htmlspecialchars(). Even if you created the file yourself, escaping the output means an unexpected < or quotation mark remains text rather than becoming markup.
Decide what blank lines mean
If you merely want a list of nonblank entries, you can filter after reading. Be deliberate: removing a blank line shifts the position of every subsequent entry. That is fine for a list of names but wrong if line 7 must always correspond to the seventh day of the month.
<?php
$lines = file($path, FILE_IGNORE_NEW_LINES);
if ($lines === false) {
throw new RuntimeException('Could not read the file.');
}
$items = array_values(array_filter($lines, static fn($line) => trim($line) !== ''));
array_values() rebuilds a zero-based list after filtering. If positions carry meaning, keep the original array and check whether a selected line is blank. Also decide whether spaces at either end are significant. trim() is useful for URLs copied with extra whitespace, but it should not be applied blindly to data where leading spaces are meaningful.
Stream a large file with fgets()
For a much larger file, loading every line into an array can waste memory. Open a file handle and read each line inside a loop. The file handle advances as lines are read, so the script holds only a small portion of the file at once. Close the handle when you finish, including if a later operation fails.
<?php
$handle = fopen($path, 'rb');
if ($handle === false) {
throw new RuntimeException('Could not open the file.');
}
try {
$number = 0;
while (($line = fgets($handle)) !== false) {
++$number;
$line = rtrim($line, "\r\n");
// Process $number and $line here.
}
} finally {
fclose($handle);
}
The strict comparison to false matters: a line containing 0 or an empty line is still a line. The rtrim() call removes only carriage returns and newlines, leaving ordinary spaces in the data. For a genuinely huge file, a streaming loop also lets you stop early after finding the entry you need.
Handle the real-world edges
Check whether the file exists, is readable by the web-server user, and uses the expected encoding. If you are reading a list edited on another operating system, line endings may differ; the examples above handle common line endings. If another process writes the file while you read it, consider writing a replacement file and renaming it atomically, or using locking in a more complex workflow.
Never build a file path directly from unchecked visitor input. A value such as ../ can change which file is opened. Map a limited set of safe names to predetermined paths instead. If the content will be shown to visitors, continue to escape it for the output context. Reading a file safely and displaying it safely are related but separate tasks.
A short daily URL list is a good case for file(); a multi-gigabyte log is a case for streaming. The simplest method that preserves your data’s meaning is usually the best choice. Next, see how to match one physical line to the day of the month.
Reference: PHP file() and PHP fgets().