PHPFile Handling Overview

File Handling Overview

Almost every real PHP application eventually needs to read from or write to a file — a log, a cache, a configuration file, an uploaded document, or a report generated for a user to download. PHP's file handling functions date back to its earliest versions and follow a simple, low-level model borrowed from C: you open a file to get a "handle" (also called a resource or stream), you read or write through that handle, and then you close it. This page covers that core model — fopen(), fread(), fwrite(), fclose() — along with the file modes that control what you're allowed to do, and a preview of the simpler helper functions that cover most everyday cases without needing a handle at all.

The handle-based model

fopen() takes a file path and a mode string, and returns a resource that represents an open connection to that file. Every subsequent operation — reading, writing, checking for end-of-file — happens through that resource, not through the path directly. When you're done, fclose() releases the underlying operating system file handle. This matters more than it might seem: operating systems limit how many files a single process can have open at once, and an unclosed handle also risks losing buffered writes that haven't been flushed to disk yet.

basic-fopen.php

PHP
<?php

$handle = fopen('notes.txt', 'w');

if ($handle === false) {
    die('Could not open file for writing.');
}

fwrite($handle, "First line\n");
fwrite($handle, "Second line\n");

fclose($handle);

echo 'Done writing.';
Done writing.
File modes

The second argument to fopen() is a mode string that controls both the type of access you get and what happens to any existing content. Getting this wrong is one of the most common file-handling mistakes in PHP — opening in the wrong mode can silently erase a file you meant to append to.

  • r — read-only. The file must already exist, and the cursor starts at the beginning.

  • w — write-only. Creates the file if it doesn't exist, and truncates it to zero bytes if it does.

  • a — append-only. Creates the file if needed; the cursor starts at the end, and existing content is preserved.

  • r+ — read and write, without truncating. The file must exist.

  • w+ — read and write, but truncates the file first, just like w.

  • a+ — read and append, preserving existing content, with reads able to start from the beginning.

  • x — like w, but fails instead of overwriting if the file already exists.

w truncates immediately
Calling `fopen($path, 'w')` empties the file the moment it succeeds — before you've written a single byte. If you meant to add to an existing log or data file, use `a` (append) instead. There's no confirmation prompt and no way to recover the original content once it's gone.
Reading with fread()

fread($handle, $length) reads up to $length bytes from the current position of the file pointer and advances the pointer by that amount. To read an entire file this way, you typically loop until feof() reports that the pointer has reached the end.

fread-example.php

PHP
<?php

$handle = fopen('notes.txt', 'r');

if ($handle === false) {
    die('Could not open file for reading.');
}

$contents = '';
while (!feof($handle)) {
    $contents .= fread($handle, 8192);
}

fclose($handle);

echo $contents;
First line
Second line
Always check fopen()'s return value

fopen() doesn't throw an exception when it fails — a missing directory, a permissions problem, or a locked file all cause it to return false while emitting a warning. Code that assumes the handle is valid and immediately calls fwrite() or fread() on it will produce confusing follow-on errors, because those functions expect a resource, not false. Checking the return value explicitly turns a cryptic failure two lines later into a clear, actionable error at the point where things actually went wrong.

check-fopen.php

PHP
<?php

$path = '/var/log/app/does-not-exist/log.txt';
$handle = fopen($path, 'a');

if ($handle === false) {
    error_log("Failed to open log file: $path");
    exit(1);
}

fwrite($handle, 'Started up' . PHP_EOL);
fclose($handle);
Path input from users
If any part of the path comes from user input — a filename typed into a form, a query parameter, an uploaded file's original name — never concatenate it directly into a filesystem path. A value like `../../etc/passwd` can walk outside the directory you intended (a path traversal attack). Validate the input against a whitelist of allowed characters or known filenames, and resolve the final path with `realpath()` to confirm it still lives inside the expected directory before opening it.
Simpler helpers: a preview

The fopen/fread/fwrite/fclose sequence gives you fine control — reading in chunks, writing incrementally, seeking to a specific position — but for the common case of "read this whole file" or "write this whole string to a file," PHP offers file_get_contents() and file_put_contents(). They wrap the same handle-based mechanics internally, open and close the handle for you, and return either the file's contents or the number of bytes written. The following two pages, on reading and writing files, cover these helpers in depth.

helpers-preview.php

PHP
<?php

// Equivalent to the fopen/fread/fclose loop above
$contents = file_get_contents('notes.txt');

// Equivalent to fopen/fwrite/fclose with mode 'w'
file_put_contents('notes.txt', "Overwritten content\n");

echo $contents;
Tip
Reach for `file_get_contents()` and `file_put_contents()` by default — they're shorter, harder to misuse, and handle closing the file for you even if an error occurs partway through. Drop down to `fopen()`/`fread()`/`fwrite()` only when you need to process a file incrementally, such as streaming a large file line by line instead of loading the whole thing into memory at once.