22. PHP File Handling
What you will learn
- How to read and write text files
- How to check whether a file exists
- How to read a file line by line
- How to handle uploads safely
Why work with files?
PHP can read and write files on the server: logs, settings, exported data, and uploaded pictures. For lasting, searchable data, a database (next lesson) is better, but files are handy for small jobs.
Practise in a folder you created, such as practice/data. The user running the web server needs permission to write there. Never let visitors choose a file name or a folder.
Reading a whole file
PHP code: read-file.php
<?php
$path = __DIR__ . "/data/notes.txt";
if (!is_file($path)) {
echo "The file does not exist yet.";
} else {
$text = file_get_contents($path);
echo nl2br(htmlspecialchars($text, ENT_QUOTES, "UTF-8"));
}
Output if notes.txt contains two lines: First note, then Second note
First note
Second note
file_get_contents() returns the whole file as a string, or false on failure. is_file() checks that the path exists and is a file. Escape the text before printing, as the file may contain anything.
Writing a file
PHP code: write-file.php
<?php
$dir = __DIR__ . "/data";
$path = $dir . "/notes.txt";
if (!is_dir($dir)) {
mkdir($dir, 0755, true);
}
$result = file_put_contents($path, "First note\n", LOCK_EX);
echo $result === false ? "Could not write the file." : "Wrote $result bytes.";
Output
Wrote 11 bytes.
file_put_contents()replaces the file. It returns the number of bytes written, orfalse.LOCK_EXstops two requests writing at the same moment.- Always check for
false.
Appending
PHP code
<?php
$path = __DIR__ . "/data/log.txt";
$line = date("Y-m-d H:i:s") . " Page viewed\n";
file_put_contents($path, $line, FILE_APPEND | LOCK_EX);
echo "Logged.";
Output (and a new line is added to log.txt each time)
Logged.
Reading line by line
For big files, read a line at a time instead of loading everything into memory.
PHP code
<?php
$path = __DIR__ . "/data/notes.txt";
$handle = fopen($path, "r");
if ($handle === false) {
echo "Could not open the file.";
} else {
$number = 1;
while (($line = fgets($handle)) !== false) {
echo $number . ": " . htmlspecialchars(rtrim($line), ENT_QUOTES, "UTF-8") . "<br>";
$number++;
}
fclose($handle);
}
Output for a file with two lines
1: First note
2: Second note
fopen() opens a file with a mode: "r" to read, "w" to write (erasing it), "a" to append. Always fclose() when you finish. The shortcut file($path, FILE_IGNORE_NEW_LINES) returns all lines as an array.
Handy file functions
| Function | Does |
|---|---|
file_exists($p), is_file($p), is_dir($p) |
Check what exists |
filesize($p) |
Size in bytes |
unlink($p) |
Delete a file |
copy($a, $b), rename($a, $b) |
Copy or move |
scandir($dir) |
List a folder |
basename($p) |
The file name part of a path |
CSV and JSON files
PHP code
<?php
$path = __DIR__ . "/data/settings.json";
$settings = ["theme" => "dark", "items" => 10];
file_put_contents($path, json_encode($settings, JSON_PRETTY_PRINT), LOCK_EX);
$loaded = json_decode(file_get_contents($path), true);
echo "Theme: " . $loaded["theme"] . ", items: " . $loaded["items"];
Output
Theme: dark, items: 10
For spreadsheet-style files, fputcsv() and fgetcsv() write and read CSV rows correctly, including commas and quotes inside values.
File uploads
Uploads are one of the riskiest features. An attacker may upload a PHP file and then run it. The rules:
- The form needs
method="post"andenctype="multipart/form-data". - Check
$_FILES["file"]["error"]equalsUPLOAD_ERR_OK. - Check the size, and check the real type of the content, not just the name or the type the browser claims.
- Never use the visitor’s file name. Generate your own random name with a fixed, safe extension.
- Save into a folder where PHP files cannot run, ideally outside the public folder.
- Use
move_uploaded_file(), which only accepts genuine uploads.
HTML code: upload form
<form method="post" enctype="multipart/form-data">
<label for="pic">Choose a picture (JPG or PNG, up to 2 MB)</label>
<input type="file" id="pic" name="pic" accept="image/jpeg,image/png">
<button type="submit">Upload</button>
</form>
PHP code: processing the upload
<?php
$message = "";
if ($_SERVER["REQUEST_METHOD"] === "POST") {
$file = $_FILES["pic"] ?? null;
if ($file === null || $file["error"] !== UPLOAD_ERR_OK) {
$message = "Upload failed. Please try again.";
} elseif ($file["size"] > 2 * 1024 * 1024) {
$message = "The file is too large (2 MB maximum).";
} else {
$info = getimagesize($file["tmp_name"]); // false if not an image
$types = [IMAGETYPE_JPEG => "jpg", IMAGETYPE_PNG => "png"];
if ($info === false || !isset($types[$info[2]])) {
$message = "Only JPG and PNG pictures are allowed.";
} else {
$name = bin2hex(random_bytes(16)) . "." . $types[$info[2]];
$target = __DIR__ . "/uploads/" . $name;
if (move_uploaded_file($file["tmp_name"], $target)) {
$message = "Picture saved.";
} else {
$message = "Could not save the picture.";
}
}
}
}
echo htmlspecialchars($message, ENT_QUOTES, "UTF-8");
Possible messages
Picture saved. / The file is too large (2 MB maximum). / Only JPG and PNG pictures are allowed.
The server also has its own upload limits (upload_max_filesize and post_max_size in the PHP settings), which can be lower than yours. On WordPress, use the Media Library instead of writing your own upload code.
Security reminders
- Never build a file path from visitor input. A value such as
../../secretcan climb out of your folder (path traversal). Use an allowed list, orbasename()plus a fixed folder. - Do not store sensitive files in a public folder.
- Check that writing succeeded, and handle errors without showing file paths to visitors.
Common mistakes
- Not checking that a file exists before reading it.
- Using
file_put_contents()and accidentally overwriting a file instead of appending. - Forgetting that the web server needs permission to write.
- Trusting the uploaded file’s name or claimed type.
- Forgetting
enctype="multipart/form-data", so$_FILESstays empty. - Leaving a file handle open.
Exercise
Write a small page counter. It reads a number from data/count.txt (use 0 if the file is missing), adds 1, saves it, and prints This page has been viewed N time(s).
Show solution
<?php
$dir = __DIR__ . "/data";
$path = $dir . "/count.txt";
if (!is_dir($dir)) {
mkdir($dir, 0755, true);
}
$count = is_file($path) ? (int) file_get_contents($path) : 0;
$count++;
file_put_contents($path, (string) $count, LOCK_EX);
echo "This page has been viewed $count time(s).";
Reload the page a few times to watch the number grow. (Two visitors at the same instant could still collide, which is why real counters use a database.)
Knowledge check
1. Which function reads a whole file into a string?
file_get_contents()
2. How do you add to the end of a file instead of replacing it?
Pass FILE_APPEND to file_put_contents().
3. Why must you never use an uploaded file’s original name?
It can be forged to overwrite files or run code. Generate your own safe name.
Summary
- Use
file_get_contents,file_put_contents, andfopenwithfgetsto read and write files. - Check results, escape output, and never trust visitor-supplied paths.
- Handle uploads with strict checks and your own file names, in a safe folder.