Tutorials Logic, IN info@tutorialslogic.com

PHP File Handling: Read, Write, Lock, and Protect Paths

PHP File Streams

PHP file handling uses streams whose paths, wrappers, modes, byte counts, metadata, locks, and lifetimes have observable failure and concurrency behavior. Convenience functions are safe only within explicit size and trust limits.

Reliable file work anchors trusted roots, prevents traversal and link escapes, streams bounded data, handles partial writes, replaces complete temporary files, uses advisory locks deliberately, and verifies cleanup and platform behavior.

Read a File

Read Required Text

Read Required Text
<?php
$path = __DIR__ . '/data/message.txt';
$content = file_get_contents($path);

if ($content === false) {
    throw new RuntimeException("Could not read {$path}");
}

echo trim($content);

file_get_contents() returns false on failure, so strict comparison is required.

Write with a Lock

Append an Audit Line

Append an Audit Line
<?php
$path = __DIR__ . '/data/audit.log';
$line = date(DATE_ATOM) . " lesson_viewed\n";
$bytes = file_put_contents($path, $line, FILE_APPEND | LOCK_EX);

if ($bytes === false) {
    throw new RuntimeException('Audit log write failed.');
}
  • LOCK_EX coordinates writers on the same filesystem but does not make a multi-step workflow transactional.

Stream Handles

Use fopen() when data should be processed incrementally or when you need explicit stream control. Close handles in finally so an exception does not leak the resource.

Read CSV Rows

Read CSV Rows
<?php
$handle = fopen(__DIR__ . '/data/users.csv', 'rb');
if ($handle === false) {
    throw new RuntimeException('CSV could not be opened.');
}

try {
    while (($row = fgetcsv($handle)) !== false) {
        echo implode(' | ', $row) . PHP_EOL;
    }
} finally {
    fclose($handle);
}

Path Safety

  • Generate server-side file names for uploads and stored records.
  • Resolve a permitted base directory and reject paths that escape it.
  • Keep uploaded content outside directories where PHP can execute.
  • Use a database when records need concurrent updates, queries, constraints, or transactions.

Atomic and Safe Files

For an application-owned update, write the complete new content to a temporary file in the same directory, flush and close it, then replace the target according to the platform’s supported semantics. Use locking when cooperating processes follow the same protocol, but do not treat flock as a universal distributed lock.

Never combine an upload’s original name with a writable public path. Generate a server-owned name, validate size and content with trusted APIs, store outside the executable web root when possible, and serve it through a controlled download response.

Path Ownership

File handling begins with a trusted root and a declared purpose: configuration, user upload, export, cache, log, or application asset. Do not let one general helper read and write arbitrary process paths.

Anchor application-owned paths with `__DIR__` or injected absolute roots. The current working directory can differ among web, CLI, tests, and workers, while URL paths do not follow filesystem rules.

Map external identifiers to generated or allowlisted names. Normalizing dot-dot text alone does not prevent traversal, alternate separators, stream wrappers, links, or encoding tricks. Verify the resolved target remains under the intended root.

Keep writable data outside executable source and the public document root when possible. Apply least-privilege filesystem permissions and separate tenants or data classes when disclosure impact differs.

  • Give each file operation one trusted root and purpose.
  • Use stable absolute application paths.
  • Map external identifiers instead of accepting paths.
  • Separate writable data from source and public files.

Stream Modes

`fopen` returns a stream resource or false and interprets a mode that controls reading, writing, truncation, creation, append position, and exclusivity. Select the mode from the data-loss policy before opening the file.

A write mode can truncate immediately, before an advisory lock is acquired. When existing content must remain until ownership is established, open without premature truncation, obtain the lock, then truncate deliberately or use a temporary-file replacement strategy.

Binary mode matters for portable byte behavior on systems that translate text line endings. Treat uploaded media, archives, hashes, and protocol payloads as binary and do not assume string length equals visible characters.

Stream wrappers can make a filename refer to URLs, memory, compression, or other resources. Permit only expected schemes at trust boundaries and configure network timeouts and TLS separately when remote access is an intentional feature.

  • Choose modes from overwrite and creation policy.
  • Avoid truncating before acquiring required ownership.
  • Use binary-safe handling for byte-oriented data.
  • Allow only expected stream schemes.

Bounded Reads

Convenience functions can load an entire file into memory. Use them only after checking an acceptable maximum and considering races between metadata checks and reads. Stream large or untrusted files incrementally under a hard byte limit.

`fread` may return fewer bytes than requested, and line functions include delimiter and end-of-file behavior that must be checked. Loop on the actual result, distinguish false from an empty string where the API requires it, and stop on errors.

CSV, JSON Lines, and other record formats need parser-aware reading rather than manual delimiter splitting. Enforce maximum record length, field count, encoding, and total records before constructing domain objects.

A file can change while being read. Decide whether the feature needs a snapshot, lock, immutable generated name, content hash, or tolerance for change. Never use a pre-read check as proof that later content is safe.

  • Limit bytes before and during reading.
  • Handle short reads and false distinctly.
  • Use format parsers with record limits.
  • Define consistency when files can change concurrently.

Reliable Writes

`fwrite` can write fewer bytes than supplied, so critical stream code should advance by the returned count until complete or failed. Check every result and never report success only because opening the file worked.

For replaceable configuration or cache data, write a complete temporary file in the same filesystem, flush as required, set appropriate permissions, and rename it over the target. Readers then see the old or new complete file rather than a partial body.

Rename atomicity and overwrite behavior depend on filesystem and platform boundaries. Test the deployment filesystem, keep temporary and target files on the same volume, and define recovery for a leftover temporary file.

Durability is stronger than PHP returning from a write. Applications that promise crash-resistant persistence need explicit flush and filesystem guarantees and should usually prefer a transactional database for critical records.

  • Handle partial writes until completion or failure.
  • Replace files through same-filesystem temporary output.
  • Test rename behavior on the deployment platform.
  • Match durability claims to actual storage guarantees.

Locks and Concurrency

`flock` provides advisory locking on supported local filesystems. Every cooperating writer must follow the same protocol; the lock does not automatically stop code that ignores it or guarantee equivalent behavior on every network filesystem.

Acquire a shared lock for a consistent read or exclusive lock for mutation where the feature requires it, check acquisition failure, hold it for the smallest complete critical section, and release it through cleanup paths.

Do not hold a file lock during remote calls or user interaction. Compute data first, lock, verify any version assumption, write or replace, then unlock. Add a timeout or non-blocking policy when indefinite waiting would exhaust workers.

File locking does not solve multi-record transactions, indexing, querying, or distributed coordination. Move those workloads to a database or service designed for concurrent state instead of layering increasingly complex lock files.

  • Use one documented advisory-lock protocol.
  • Check lock acquisition and release on every path.
  • Keep critical sections short and bounded.
  • Use transactional storage for complex concurrent state.

Metadata and Links

Filesystem checks such as existence, type, permissions, size, and modification time are snapshots that can become stale immediately. Open the target safely and validate the resulting handle when security or correctness depends on it.

Symbolic links can escape an apparently safe directory or redirect between check and use. For sensitive operations, control the storage tree, avoid following untrusted links, and verify canonical containment under the trusted root.

File extensions and names do not prove content type. Inspect data with an appropriate parser or detector, maintain an allowlist for the feature, and serve downloads with safe content type and disposition headers.

Permissions are affected by process identity, directory traversal rights, masks, access-control lists, and platform behavior. Deploy a dedicated runtime account and test the exact create, read, replace, and delete operations it needs.

  • Treat metadata checks as temporary observations.
  • Control and verify symbolic-link behavior.
  • Validate content independently of extensions.
  • Test permissions as the real runtime identity.

Cleanup and Tests

Close handles in a finally block or equivalent ownership boundary, release locks, remove abandoned temporary files, and preserve the original exception when cleanup also fails. Do not rely solely on process shutdown for scarce handles in long-running workers.

Test missing files, empty files, maximum bytes, short reads and writes through test doubles where feasible, permission denial, disk-full behavior, invalid encoding, traversal attempts, links, lock contention, and interruption before rename.

Run platform tests on each supported operating system and the production-like filesystem. Case sensitivity, separators, rename replacement, permissions, and advisory locks can differ despite identical PHP source.

Log operation names, safe logical identifiers, byte counts, elapsed time, and failure categories without exposing private paths or file contents. Monitor temporary-file growth and repeated lock timeouts as early signs of incomplete cleanup or overload.

  • Release handles and locks through guaranteed cleanup.
  • Exercise storage exhaustion and interruption paths.
  • Verify filesystem behavior on supported platforms.
  • Monitor bounded metadata without leaking content.
Before you move on

Mastery Check

5 checks
  • Map logical identifiers to one trusted storage root.
  • Choose stream mode and byte limits before opening.
  • Check every read, write, lock, flush, and rename result.
  • Separate writable data from executable public source.
  • Test contention, interruption, exhaustion, links, and cleanup.

File Failure Check

0 of 2 checked

Q1. How should file_get_contents() failure be checked?

Q2. What does LOCK_EX protect?

Try this next

Handle a Real File

0 of 2 completed

  1. Process a text file with fopen() and ensure fclose() runs in finally.
  2. Design a safe mapping from permitted report names to fixed server paths.
Browse Free Tutorials

Explore 500+ free tutorials across 20+ languages and frameworks.