Reading one text file is straightforward until several requests change it, a folder belongs to another account, or a visitor supplies part of the path. Advanced PHP file handling is mostly about those surrounding conditions. The script runs as a process with an operating-system identity that determines what it can read and write. A filesystem can fail even when your PHP syntax is correct. This lesson explains streams, permissions, cooperative file locks, and careful path construction. You will update a local counter safely and learn why permission checks are useful diagnostics rather than promises that later operations will succeed.
Identify the account running PHP
On a web server, PHP commonly runs under a service account rather than your personal login. A file that opens in your editor may still be unreadable to that account. On Unix-like systems, permission bits describe owner, group, and other users, with separate read, write, and execute bits. Directory execute permission allows traversal; writing a new file also requires access to the containing directory. Values such as 0640 and 0750 are examples, not universal deployment instructions. Your owner and group choices determine whether they work. Windows uses access control lists with inheritance rules, so chmod() does not reproduce the full Unix permission model there. Diagnose the account, directory, and inherited access before changing settings. Giving every user write access can hide an immediate failure while creating a larger problem.
Constrain file paths
Use a configured storage directory or __DIR__ rather than an unpredictable working directory. A command-line script and a web request may start from different directories. Never append an unchecked filename from a query string to a storage root. A value such as ../config.php can escape the intended folder. Prefer an application-owned identifier mapped to a known filename, or a strict allowlist of names. realpath() can resolve an existing target, but it returns false for a nonexistent path and needs a separator-aware containment check. A plain prefix comparison can confuse /data/store with /data/store-other. Symlinks and changes between checking and opening add complexity. For important access control, constrain the process itself and avoid granting access to files the application does not need.
Process streams and check failures
file_get_contents() suits small bounded files, but loading a large export consumes memory. fopen() followed by fgets() or a format-specific reader lets you process data gradually. Check fopen() for false before passing its result elsewhere. A readable check may improve an error message, but another process can change the file afterward. The actual operation result remains authoritative. fwrite() returns the number of bytes written or false, and a general writer may need a loop when only part of a buffer is written. fclose() releases the handle; use finally when exceptions could skip cleanup. Distinguish end of file from a failed read when precise reporting matters. Treat warnings as diagnostic evidence rather than suppressing all of them with the @ operator.
Lock the entire change
LOCK_EX on file_put_contents() protects its write but not an earlier read. Two visitors can both read ten, compute eleven, and write eleven. flock() with an exclusive lock lets cooperating processes guard reading, calculation, truncation, and writing together. Open with a mode that does not truncate before the lock; c+ opens or creates a counter file without immediate truncation. Lock before reading and unlock after flushing. Locks are generally advisory, so other writers must follow the same convention. Network filesystems and operating systems can differ in their behavior. A lock does not make a file a database. For frequent concurrent changes, a database increment operation is often a clearer and more reliable design.
Replace complete documents carefully
For a settings snapshot, write a temporary file in the same directory, verify success, then rename it into place where your platform supports the desired atomic replacement behavior. Readers see a complete old or new document instead of half a document. The same-directory choice helps keep the rename on one filesystem, but replacement and durability guarantees are platform-dependent. A crash may still require recovery; atomic visibility does not imply guaranteed persistence to disk. Preserve appropriate permissions on the replacement and clean failed temporary files. Avoid deleting the old file first, which creates an interval with no valid document. If the file coordinates critical business transactions, choose storage designed for that purpose instead of extending a fragile homemade protocol.
Diagnose a failed write methodically
Check the exact target and its parent directory without exposing paths publicly. Confirm free disk space, service-account access, and whether deployment made the directory read-only. Containers and hosted environments may provide temporary filesystems that disappear when an instance restarts. Your successful local write does not prove persistent production storage exists. Distinguish permanent access errors from temporary storage exhaustion before retrying. Repeatedly retrying a permission failure wastes resources and obscures the useful message. Log a stable application operation name and a request identifier, then return a simple failure to the visitor. Keep backups for valuable documents and make recovery procedures part of the design rather than assuming a successful write can never be lost.
Practice task and worked solution
Design a JSON snapshot writer that never exposes a partial document. Encode complete data with JSON_THROW_ON_ERROR before touching the current file. Create a unique temporary name within a trusted directory, write the bytes, and compare the byte count with the encoded length. Set access appropriate for the reader. Rename the temporary file into place using replacement behavior verified on your deployment. On failure, preserve the current snapshot and clean only the temporary file created by this operation. The solution distinguishes encoding, write, and replacement failures. Test permission denial and a full disk in an isolated environment. Use a database if several writers must coordinate related records.
Continue learning
Review file handling and exceptions for file modes and finally-based cleanup.
The PHP filesystem manual documents operations and platform limitations.
Advanced File Handling and File Permissions Examples
Each example shows the code, the output it produces and what each part does.
Increment a counter under a lock
Run in a writable local directory using PHP CLI. The path is application-owned and the handle is closed on success or failure.
A first successful run with no counter file prints 1; the second prints 2. Access failures depend on the directory and filesystem. General writers should also check partial byte counts; this example writes a tiny counter.
<?php
$handle = fopen(__DIR__ . '/counter.txt', 'c+');
if ($handle === false) throw new RuntimeException('Cannot open counter');
try {
if (!flock($handle, LOCK_EX)) throw new RuntimeException('Cannot lock counter');
$raw = stream_get_contents($handle);
if ($raw === false || ($raw !== '' && !ctype_digit(trim($raw)))) {
throw new RuntimeException('Invalid counter data');
}
$next = ($raw === '' ? 0 : (int) trim($raw)) + 1;
rewind($handle);
if (!ftruncate($handle, 0) || fwrite($handle, (string) $next) === false) {
throw new RuntimeException('Cannot write counter');
}
fflush($handle);
flock($handle, LOCK_UN);
echo $next . PHP_EOL;
} finally {
fclose($handle);
}First successful run: 1
Second successful run: 2Advanced File Handling and File Permissions FAQ
Should chmod(0777) fix access?
No. 0777 lets every user on the system read, change and run the files, which turns a permissions problem into a security problem. Diagnose which user PHP runs as and who owns the folder, then grant only the access needed, for example write access to an uploads or cache directory for the web server's user.
Does is_writable() guarantee success?
No. It reports the situation at the moment you ask, but the later write can still fail: another process may change permissions, the disk may fill up, or a quota may be reached. Always check the return value of the actual write operation and handle a failure there.
Does a locked write protect a previous read?
Only when the lock spans both. If you read a counter, release the file, and then lock it to write the new value, another request can change the file in between and its update is lost. Hold one exclusive lock from the read through the write, or use a database for values updated concurrently.
Is rename always atomic?
Not everywhere. On the same local filesystem, rename() usually replaces the target in one step, which is why "write to a temporary file, then rename" is a common safe-save pattern. Across filesystem boundaries, PHP may have to copy and delete instead, so verify platform and filesystem behaviour before relying on it.
Advanced File Handling and File Permissions Exercises
Answer each one and check it instantly. If you get stuck, open the hint before the answer.
A visitor supplies ../config.php as a report name. Choose a safe design.
Advanced File Handling and File Permissions Quiz
2 questions to check what you learned.
Why use c+ before locking?
- It encrypts files
- It grants public access
- It locks automatically
- It opens or creates without truncating first
Why do two independent locked writes lose an increment?
- Both requests can read the same old value first
- Locks double numbers
- PHP cannot store integers
- The extension changes arithmetic