PHP File Handling

Read, write, append, and stream files. Master file_get_contents, fopen/fread/fwrite, directories, uploads, and CSV - the everyday file operations.

Intermediate 9 min read 10 examples

Read & Write Whole Files

PHP
<?php
// Read entire file into a string
$contents = file_get_contents("data.txt");

// Read into an array - one line per element
$lines = file("data.txt", FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);

// Write entire file (overwrites)
file_put_contents("out.txt", "Hello world\n");

// Append
file_put_contents("log.txt", "Entry\n", FILE_APPEND | LOCK_EX);

// Read from URL (allow_url_fopen must be on)
$json = file_get_contents("https://api.example.com/users");

Stream API (fopen / fread / fwrite)

ModeMeans
rRead only - file must exist
r+Read & write - file must exist
wWrite - truncate existing or create new
w+Read & write - truncate or create
aAppend - position at end, create if missing
a+Read & append - create if missing
xCreate new - fail if exists
bBinary mode (append to any above on Windows)
PHP
<?php
$fp = fopen("data.bin", "rb");
if ($fp === false) throw new RuntimeException("Cannot open");

try {
    while (!feof($fp)) {
        $chunk = fread($fp, 8192);   // 8KB chunks
        // process $chunk
    }
} finally {
    fclose($fp);
}

// Write
$fp = fopen("out.log", "a");
fwrite($fp, "Log entry\n");
fclose($fp);

Reading Line by Line

PHP
<?php
$fp = fopen("huge.log", "r");
try {
    while (($line = fgets($fp)) !== false) {
        if (str_contains($line, "ERROR")) {
            echo $line;
        }
    }
} finally {
    fclose($fp);
}

// Modern alternative: SplFileObject - memory-efficient iterator
foreach (new SplFileObject("huge.log") as $line) {
    if (str_contains($line, "ERROR")) echo $line;
}

Directories

PHP
<?php
// Create
mkdir("logs/archive", 0755, recursive: true);

// List entries
foreach (scandir(__DIR__) as $entry) {
    if ($entry === "." || $entry === "..") continue;
    echo $entry . "\n";
}

// Glob (pattern matching)
foreach (glob("logs/*.log") as $file) {
    echo $file . "\n";
}

// Recursive walk
$it = new RecursiveIteratorIterator(
    new RecursiveDirectoryIterator(__DIR__, FilesystemIterator::SKIP_DOTS)
);
foreach ($it as $file) {
    if ($file->isFile()) echo $file->getPathname() . "\n";
}

// Remove
unlink("temp.txt");
rmdir("empty-dir");

File Metadata

PHP
<?php
$path = "data.txt";

if (!file_exists($path)) die("Missing");

echo filesize($path);              // bytes
echo filemtime($path);             // last modified unix timestamp
echo date("Y-m-d H:i:s", filemtime($path));
echo pathinfo($path, PATHINFO_EXTENSION);    // txt
echo dirname($path);
echo basename($path);
echo mime_content_type($path);     // text/plain

echo realpath($path);              // absolute path with symlinks resolved
echo is_readable($path) ? "yes" : "no";

File Uploads

PHP
<?php
if (!isset($_FILES["upload"])
    || $_FILES["upload"]["error"] !== UPLOAD_ERR_OK
) {
    die("Upload failed");
}

$file = $_FILES["upload"];

// Size limit
if ($file["size"] > 5 * 1024 * 1024) die("Too large (max 5MB)");

// Type check by content, not extension
$mime = mime_content_type($file["tmp_name"]);
$allowed = [
    "image/jpeg" => "jpg",
    "image/png"  => "png",
    "application/pdf" => "pdf",
];
if (!isset($allowed[$mime])) die("Type not allowed");

// Safe filename
$name = bin2hex(random_bytes(16)) . "." . $allowed[$mime];
$dest = __DIR__ . "/uploads/$name";

if (!move_uploaded_file($file["tmp_name"], $dest)) {
    die("Save failed");
}
echo "Saved as $name";
Never trust the uploaded filename

An attacker can submit ../../etc/passwd or shell.php.jpg. ALWAYS generate a fresh random filename and ALWAYS check the MIME type with mime_content_type(), not the file extension.

CSV Files

PHP
<?php
// Read
$fp = fopen("users.csv", "r");
$headers = fgetcsv($fp);
while (($row = fgetcsv($fp)) !== false) {
    $user = array_combine($headers, $row);
    echo $user["email"] . "\n";
}
fclose($fp);

// Write
$fp = fopen("export.csv", "w");
fputcsv($fp, ["id", "name", "email"]);
foreach ($users as $u) {
    fputcsv($fp, [$u->id, $u->name, $u->email]);
}
fclose($fp);

File Locking

Prevent concurrent writes from corrupting a file:

PHP
<?php
$fp = fopen("counter.txt", "c+");
flock($fp, LOCK_EX);                 // exclusive lock - blocks others

$count = (int) stream_get_contents($fp);
$count++;

rewind($fp);
ftruncate($fp, 0);
fwrite($fp, (string) $count);
fflush($fp);

flock($fp, LOCK_UN);
fclose($fp);

Next Steps

Frequently Asked Questions

For small/medium files, file_get_contents() is faster - one syscall. For huge files (GBs), a streaming fread loop avoids loading everything into memory at once.

file_exists() works for both files and directories. is_file() is stricter (file only), is_dir() for directories. is_readable()/is_writable() also check permissions.

Usually permissions: the web server user (www-data, apache) doesn't have write access to the target directory. Check with ls -la and fix with chmod 755 on the directory or chown.

Open with fopen, read in chunks with fread($fp, 8192) or line by line with fgets($fp). Wrap in try/finally to ensure fclose runs. For huge logs, consider SplFileObject which is memory-efficient.

HTTP with the standard $_FILES mechanism for user-facing apps. FTP only for trusted admin/CLI workflows. Always validate uploaded files' MIME type, size, and rename to a random filename.