PHP File Open/Read
Reading files is one of the most common file operations: loading configuration, processing logs, importing data, or displaying stored text. PHP offers several ways to read, from one-line helpers that load an entire file to streams that read a few bytes at a time. This lesson explains when to use each and how to read safely and efficiently.
All examples read this sample log file, app.log:
2026-10-15 10:00:01 INFO User login
2026-10-15 10:02:17 ERROR Payment failed
2026-10-15 10:05:42 INFO Order created
2026-10-15 10:07:03 ERROR Email not sent
It can be created for testing with:
<?php
file_put_contents(__DIR__ . '/app.log', implode("\n", [
'2026-10-15 10:00:01 INFO User login',
'2026-10-15 10:02:17 ERROR Payment failed',
'2026-10-15 10:05:42 INFO Order created',
'2026-10-15 10:07:03 ERROR Email not sent',
]) . "\n");
Reading a Whole File
file_get_contents() — Into One String
<?php
$content = file_get_contents(__DIR__ . '/app.log');
echo strlen($content) . " bytes\n";
echo substr_count($content, 'ERROR') . " errors\n";
Output
157 bytes
2 errors
file() — Into an Array of Lines
<?php
$lines = file(__DIR__ . '/app.log', FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
echo count($lines) . " lines\n";
echo "Last: " . end($lines) . "\n";
Output
4 lines
Last: 2026-10-15 10:07:03 ERROR Email not sent
| Flag | Effect |
|---|---|
FILE_IGNORE_NEW_LINES | Removes the line ending from each element |
FILE_SKIP_EMPTY_LINES | Skips empty lines (works together with FILE_IGNORE_NEW_LINES) |
Reading Part of a File
<?php
echo file_get_contents(__DIR__ . '/app.log', false, null, 20, 4) . "\n";
Output
INFO
The fourth and fifth arguments are the offset (start at byte 20) and the length (read 4 bytes).
Whole-file functions are simple and fast for small files. They load everything into memory, so they are not suitable for very large files.
Opening a File: fopen()
Syntax
fopen(string $filename, string $mode, ...): resource|false
fopen() opens a file and returns a file handle (a resource) used by the other stream functions.
Modes for Reading
| Mode | Meaning | File Must Exist? | Position |
|---|---|---|---|
r | Read only | Yes | Start |
r+ | Read and write | Yes | Start |
(Modes for writing are covered in PHP File Create/Write.) Adding b (rb) means "binary mode"; on Linux it has no effect, but it prevents line-ending conversion on Windows, so it is a good habit for binary files.
Handling a Missing File
<?php
$handle = fopen(__DIR__ . '/missing.log', 'r');
if ($handle === false) {
echo "Could not open the file.\n";
}
Output
PHP Warning: fopen(/path/missing.log): Failed to open stream: No such file or directory in /path/script.php on line 2
Could not open the file.
fopen() returns false and emits a warning. Check the file with is_readable() first when its absence is expected, and always check the result.
Reading Line by Line: fgets()
<?php
$handle = fopen(__DIR__ . '/app.log', 'r');
if ($handle === false) {
throw new RuntimeException('Cannot open log file.');
}
$counts = [];
while (($line = fgets($handle)) !== false) {
$parts = explode(' ', rtrim($line), 4);
$level = $parts[2] ?? 'UNKNOWN';
$counts[$level] = ($counts[$level] ?? 0) + 1;
}
fclose($handle);
print_r($counts);
Output
Array
(
[INFO] => 2
[ERROR] => 2
)
How It Works
fgets()reads one line including its newline character and moves the file position forward. At the end of the file, it returnsfalse.($line = fgets($handle)) !== falseassigns and checks in one step. The strict comparison is required: a line containing only"0"would otherwise end the loop.rtrim()removes the newline;explode(' ', ..., 4)splits the line into date, time, level, and message (the limit keeps spaces inside the message).fclose()releases the handle. PHP closes handles automatically at the end of the script, but closing them explicitly frees resources in long-running scripts.
Only one line is in memory at a time, so this approach works for files of any size.
The feof() Pitfall
A common older pattern is:
while (!feof($handle)) {
$line = fgets($handle);
// ...
}
feof() becomes true only after a read attempt has hit the end. The last iteration therefore calls fgets() once more, which returns false, and code that uses $line without checking processes a non-existent line. Testing the result of fgets() directly, as above, avoids this.
Reading Bytes and Moving the Position
<?php
$handle = fopen(__DIR__ . '/app.log', 'r');
echo fread($handle, 10) . "\n"; // read 10 bytes
echo ftell($handle) . "\n"; // current position
fseek($handle, 20); // jump to byte 20
echo fgets($handle); // rest of the first line
rewind($handle); // back to the start
echo fgets($handle);
fclose($handle);
Output
2026-10-15
10
INFO User login
2026-10-15 10:00:01 INFO User login
| Function | Purpose |
|---|---|
fread($handle, $length) | Read up to $length bytes |
fgetc($handle) | Read a single character (byte) |
ftell($handle) | Current position in bytes |
fseek($handle, $offset) | Move to a position (SEEK_SET, SEEK_CUR, SEEK_END) |
rewind($handle) | Move back to the start |
feof($handle) | true after reading past the end |
fread() is used for binary data and for processing large files in fixed-size chunks (for example, 8192 bytes at a time when calculating a checksum or streaming a download).
A Reusable Line Reader With a Generator
A generator function reads lines lazily and handles opening and closing in one place:
<?php
function readLines(string $path): Generator
{
$handle = fopen($path, 'r');
if ($handle === false) {
throw new RuntimeException("Cannot open $path");
}
try {
$lineNumber = 0;
while (($line = fgets($handle)) !== false) {
$lineNumber++;
yield $lineNumber => rtrim($line, "\r\n");
}
} finally {
fclose($handle);
}
}
foreach (readLines(__DIR__ . '/app.log') as $number => $line) {
if (str_contains($line, ' ERROR ')) {
echo "Line $number: " . substr($line, 26) . "\n";
}
}
Output
Line 2: Payment failed
Line 4: Email not sent
Why This Design Works Well
yieldreturns one line at a time to theforeachloop, so memory use stays constant even for gigabyte-sized files.- The line number becomes the key of each yielded value.
try ... finallyguarantees that the file is closed, even if the loop stops early withbreakor an exception occurs.rtrim($line, "\r\n")removes both Unix (\n) and Windows (\r\n) line endings, but keeps other trailing spaces.
substr($line, 26) skips the 20-character timestamp and the 6-character ERROR label. Generators are covered in PHP Generators.
SplFileObject: Object-Oriented Reading
<?php
$file = new SplFileObject(__DIR__ . '/app.log');
$file->setFlags(SplFileObject::DROP_NEW_LINE | SplFileObject::SKIP_EMPTY | SplFileObject::READ_AHEAD);
foreach ($file as $index => $line) {
echo ($index + 1) . ': ' . $line . "\n";
}
Output
1: 2026-10-15 10:00:01 INFO User login
2: 2026-10-15 10:02:17 ERROR Payment failed
3: 2026-10-15 10:05:42 INFO Order created
4: 2026-10-15 10:07:03 ERROR Email not sent
SplFileObject is iterable line by line, closes the file automatically when the object is destroyed, and throws a RuntimeException (instead of returning false) if the file cannot be opened. The flags remove line endings and skip empty lines, including the empty "line" after the final newline.
Choosing a Reading Method
| Need | Method |
|---|---|
| Small file as one string | file_get_contents() |
| Small file as an array of lines | file() |
| Large file, line by line | fgets() loop, generator, or SplFileObject |
| Binary data or chunks | fread() |
| Random access | fseek() + fread() |
| CSV rows | fgetcsv() (see PHP CSV Files) |
Common Mistakes
Loading Large Files Completely
Effect: "Allowed memory size exhausted" errors.
Fix: Stream line by line.
Loose Checks on fgets() Results
Effect: Loops stop early at a line containing "0".
Fix: while (($line = fgets($h)) !== false).
Looping With while (!feof($h))
Effect: An extra iteration with false as the "line".
Fix: Test the read result instead.
Forgetting Line Endings
Effect: Comparisons like $line === 'INFO' fail because of the trailing \n.
Fix: rtrim() or FILE_IGNORE_NEW_LINES.
Not Handling Open Failures
Effect: Warnings followed by TypeErrors when false is passed to fgets().
Fix: Check fopen() results, or use SplFileObject, which throws an exception.
Best Practices
- Use
file_get_contents()/file()only for files that comfortably fit in memory. - Read large files incrementally with
fgets(), generators, orSplFileObject. - Always check for
falsewith strict comparisons, and close handles (preferably infinally). - Remove line endings explicitly and handle both
\nand\r\n. - Build paths from
__DIR__and never open paths taken directly from user input.
Official reference: https://www.php.net/manual/en/function.fopen.php