Files, streams and the filesystem

Reading and writing files, handles and modes, stream contexts and wrappers, uploads, temporary files, permissions, and CSV or JSON round-trips.

The short way and the handle way

<?php
declare(strict_types=1);

// whole file at once - fine for small files
$html = file_get_contents("template.html");
file_put_contents("out.html", $html, LOCK_EX);   // atomic-ish, no interleaving

// line by line - the only sane way for a large log
$fh = fopen("access.log", "rb");
if ($fh === false) {
    throw new RuntimeException("Cannot open access.log");
}
flock($fh, LOCK_EX);
while (($line = fgets($fh)) !== false) {
    if (str_contains($line, " 500 ")) {
        $errors++;
    }
}
flock($fh, LOCK_UN);
fclose($fh);
ModeMeaning
rRead only; the file must exist
wWrite only; truncates the file to zero length
aAppend; creates the file, writes always go to the end
xCreate and write; fails if the file already exists
bBinary safety; add it on Windows for images and archives
⚠️
Never build a path by concatenating user input. "../../etc/passwd" is a valid filename as far as the filesystem is concerned. Resolve with realpath() and confirm the result is still inside the directory you allow, or store files under generated ids instead of names.

Streams, contexts and wrappers

<?php
// read a request body without filling memory
$raw = file_get_contents("php://input");

// keep a temporary buffer in memory and spill to disk past 2 MB
$buf = fopen("php://temp/maxmemory:2097152", "w+");
fwrite($buf, $payload);
rewind($buf);
$copy = stream_get_contents($buf);
fclose($buf);

// any wrapper can take a context: headers, timeouts, TLS options
$ctx = stream_context_create([
    "http" => [
        "method"  => "POST",
        "header"  => "Content-Type: application/json",
        "content" => json_encode($body, JSON_THROW_ON_ERROR),
        "timeout" => 5,
    ],
]);
$response = file_get_contents("https://api.example.com/orders", false, $ctx);
$fh = fopen("https://example.com/feed.csv", "rb", false, $ctx);

// temporary files and permissions
$tmp = tempnam(sys_get_temp_dir(), "imp");
chmod($tmp, 0640);
unlink($tmp);
  • A stream is a uniform interface: local files, HTTP responses, compressed data and in-memory buffers all read the same way.
  • Always give network streams a timeout; the default can block a request worker for a long time.
  • sys_get_temp_dir() respects the operating system conventions, unlike a hard-coded /tmp.
  • Remove temporary files in a finally block so a thrown exception does not leak them.

CSV and JSON round-trips

<?php
// reading CSV: fgetcsv handles quoting and embedded commas for you
$fh = fopen("sales.csv", "rb");
$header = fgetcsv($fh, escape: "");
$rows = [];
while (($row = fgetcsv($fh, escape: "")) !== false) {
    $rows[] = array_combine($header, $row);
}
fclose($fh);

// writing CSV: fputcsv quotes and escapes correctly
$out = fopen("clean.csv", "wb");
fputcsv($out, ["id", "amount", "currency"]);
foreach ($rows as $row) {
    fputcsv($out, [(int) $row["id"], (float) $row["amount"], strtoupper($row["currency"])]);
}
fclose($out);

// JSON, with errors raised instead of silently null
$data = json_decode($raw, true, 512, JSON_THROW_ON_ERROR);
$json = json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_THROW_ON_ERROR);

Both formats are lossy in different ways. CSV has no types, so an account number with a leading zero or a long integer is damaged unless you treat every field as text and convert deliberately. JSON keeps types but turns them into strings, dates and precision limits unless you decide how each field is encoded.

FAQ

How do I handle a file upload safely?
Check is_uploaded_file(), size and the real MIME type, generate your own filename, store the file outside the document root, and serve it through a script that checks permissions.
What does LOCK_EX actually protect?
It stops two processes from writing the same file at once. It does not make a multi-file update atomic, and some network filesystems ignore it - use a database or a rename into place when that matters.

Errors, exceptions and logging Databases with PDO and prepared statements

Last refreshed 2026-09-18.