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, or false.
  • LOCK_EX stops 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:

  1. The form needs method="post" and enctype="multipart/form-data".
  2. Check $_FILES["file"]["error"] equals UPLOAD_ERR_OK.
  3. Check the size, and check the real type of the content, not just the name or the type the browser claims.
  4. Never use the visitor’s file name. Generate your own random name with a fixed, safe extension.
  5. Save into a folder where PHP files cannot run, ideally outside the public folder.
  6. 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 ../../secret can climb out of your folder (path traversal). Use an allowed list, or basename() 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 $_FILES stays 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, and fopen with fgets to 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.
Scroll to Top