PHP File Create/Write
Writing files lets an application save data that must survive after the script ends: logs, reports, exports, cache files, generated configuration, and uploaded content. This lesson covers creating and writing files with fopen()/fwrite() and file_put_contents(), choosing the right mode, and writing safely when several requests write at the same time.
Writing With file_put_contents()
The simplest way to create or overwrite a file:
<?php
$file = __DIR__ . '/welcome.txt';
$bytes = file_put_contents($file, "Welcome to GoCourse!\n");
var_dump($bytes);
echo file_get_contents($file);
Output
int(21)
Welcome to GoCourse!
Syntax
file_put_contents(string $filename, mixed $data, int $flags = 0): int|false
| Behavior | Detail |
|---|---|
| File does not exist | It is created |
| File exists | Its contents are replaced |
| Return value | Number of bytes written, or false on failure |
FILE_APPEND flag | Adds to the end instead of replacing |
LOCK_EX flag | Locks the file exclusively while writing |
Array as $data | Elements are joined without separators and written |
Appending
<?php
$file = __DIR__ . '/visits.log';
file_put_contents($file, "visit 1\n", FILE_APPEND | LOCK_EX);
file_put_contents($file, "visit 2\n", FILE_APPEND | LOCK_EX);
echo file_get_contents($file);
unlink($file);
Output
visit 1
visit 2
FILE_APPEND | LOCK_EX is the standard combination for log-style files: each call adds one complete entry, and the lock prevents entries from simultaneous requests from mixing.
Writing With fopen() and fwrite()
Stream functions keep a file open while you write several pieces — useful for large or incrementally generated content.
Example
<?php
$file = __DIR__ . '/invoice.txt';
$handle = fopen($file, 'w');
if ($handle === false) {
throw new RuntimeException("Cannot open $file for writing");
}
$written = fwrite($handle, "Invoice #501\n");
$written += fwrite($handle, "Total: ₹1,250\n");
fclose($handle);
echo "Bytes written: $written\n";
echo file_get_contents($file);
Output
Bytes written: 29
Invoice #501
Total: ₹1,250
How It Works
fopen($file, 'w')creates the file (or empties an existing one) and returns a handle.fwrite()writes a string at the current position and returns the number of bytes written: 13 for the first line and 16 for the second, because the rupee sign₹takes 3 bytes in UTF-8.fclose()flushes any buffered data and closes the file. Until a file is closed (or flushed withfflush()), some data may still be in memory.
fputs() is an alias of fwrite().
File Modes for Writing
| Mode | Opens For | If the File Exists | If Missing | Position |
|---|---|---|---|---|
w | Writing | Emptied | Created | Start |
a | Writing (append) | Kept | Created | End (all writes go to the end) |
x | Writing | Fails | Created | Start |
c | Writing | Kept (not emptied) | Created | Start |
w+, a+, x+, c+ | Reading and writing | As above | As above | As above |
Choosing a Mode
w— write a fresh file, replacing any old content (reports, exports).a— add to the end (logs).x— create a new file only if it does not exist yet, which prevents accidentally overwriting existing data.c— open without emptying, typically to lock first and then decide what to write (see the counter example below).
x Mode Protects Existing Files
<?php
$file = __DIR__ . '/invoice.txt'; // already exists from the previous example
$handle = fopen($file, 'x');
var_dump($handle);
Output
PHP Warning: fopen(/path/invoice.txt): Failed to open stream: File exists in /path/script.php on line 4
bool(false)
The check and the creation happen in a single operation, so there is no gap in which another process could create the file — unlike checking file_exists() first and then writing.
Warning:
wempties a file immediately when it is opened, even if nothing is written afterwards. Opening an important file withwby mistake destroys its contents.
Writing Structured Data
JSON
<?php
$settings = ['theme' => 'dark', 'language' => 'ta', 'notifications' => ['email' => true, 'sms' => false]];
file_put_contents(
__DIR__ . '/settings.json',
json_encode($settings, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_THROW_ON_ERROR) . "\n",
LOCK_EX
);
echo file_get_contents(__DIR__ . '/settings.json');
Output
{
"theme": "dark",
"language": "ta",
"notifications": {
"email": true,
"sms": false
}
}
JSON_THROW_ON_ERROR makes encoding problems throw an exception instead of silently writing false. Reading it back is json_decode(file_get_contents($file), true).
CSV
For tabular data, use fputcsv(), which handles commas and quotes in values correctly — see PHP CSV Files.
Atomic Writes: Never Leave a Half-Written File
If a script crashes, the server restarts, or the disk fills up while a file is being written, the file can be left incomplete — a corrupted settings or cache file can break an entire application. The solution is to write to a temporary file first and then rename it over the original. On the same filesystem, rename() replaces the file in one step, so readers always see either the complete old version or the complete new version.
<?php
function saveJsonAtomically(string $path, array $data): void
{
$json = json_encode($data, JSON_PRETTY_PRINT | JSON_THROW_ON_ERROR);
$tempFile = tempnam(dirname($path), 'tmp_');
if ($tempFile === false) {
throw new RuntimeException('Cannot create a temporary file.');
}
if (file_put_contents($tempFile, $json) === false || !rename($tempFile, $path)) {
@unlink($tempFile);
throw new RuntimeException("Cannot save $path");
}
}
saveJsonAtomically(__DIR__ . '/cache.json', ['updated' => '2026-10-15', 'items' => 42]);
echo file_get_contents(__DIR__ . '/cache.json') . "\n";
Output
{
"updated": "2026-10-15",
"items": 42
}
How It Works
tempnam(dirname($path), 'tmp_')creates a uniquely named empty file in the same directory, which ensures the finalrename()happens within one filesystem.- The complete content is written to the temporary file.
rename()swaps it into place. If anything fails, the temporary file is removed and an exception is thrown; the original file stays untouched.
Locking for Read-Modify-Write Operations
LOCK_EX in file_put_contents() protects a single write. Operations that read, change, and write back a value need a lock that covers all three steps:
<?php
function incrementCounter(string $path): int
{
$handle = fopen($path, 'c+');
if ($handle === false) {
throw new RuntimeException("Cannot open $path");
}
try {
if (!flock($handle, LOCK_EX)) {
throw new RuntimeException('Cannot lock the counter file.');
}
$count = (int) stream_get_contents($handle) + 1;
ftruncate($handle, 0);
rewind($handle);
fwrite($handle, (string) $count);
fflush($handle);
return $count;
} finally {
flock($handle, LOCK_UN);
fclose($handle);
}
}
$counterFile = __DIR__ . '/downloads.count';
@unlink($counterFile);
echo incrementCounter($counterFile) . "\n";
echo incrementCounter($counterFile) . "\n";
echo incrementCounter($counterFile) . "\n";
Output
1
2
3
Step-by-Step Explanation
- Mode
c+opens the file for reading and writing, creating it if needed, without emptying it — the current value must be read first. flock($handle, LOCK_EX)waits until no other process holds a lock, then locks the file. Other requests calling this function wait at this line.- The current value is read (an empty new file gives
0), incremented, and written back after truncating the file. fflush()ensures the data is written before the lock is released infinally.
Without the lock, two simultaneous requests could both read 5 and both write 6, losing one increment. File locks are advisory: they only work if every piece of code that writes the file uses flock(). For high-traffic counters, a database or Redis is a better choice.
Practical Example: Writing a Daily Sales Report
<?php
$orders = [
['id' => 101, 'customer' => 'Asha', 'total' => 1250],
['id' => 102, 'customer' => 'Bala', 'total' => 640],
['id' => 103, 'customer' => 'Charu', 'total' => 3200],
];
$reportFile = __DIR__ . '/sales-2026-10-15.txt';
$handle = fopen($reportFile, 'w');
if ($handle === false) {
throw new RuntimeException('Cannot create report.');
}
fwrite($handle, sprintf("%-6s %-10s %10s\n", 'Order', 'Customer', 'Total'));
fwrite($handle, str_repeat('-', 28) . "\n");
$sum = 0;
foreach ($orders as $order) {
fwrite($handle, sprintf("%-6d %-10s %10s\n", $order['id'], $order['customer'], number_format($order['total'], 2)));
$sum += $order['total'];
}
fwrite($handle, str_repeat('-', 28) . "\n");
fwrite($handle, sprintf("%-17s %10s\n", 'Total', number_format($sum, 2)));
fclose($handle);
echo file_get_contents($reportFile);
Output
Order Customer Total
----------------------------
101 Asha 1,250.00
102 Bala 640.00
103 Charu 3,200.00
----------------------------
Total 5,090.00
The report is written line by line, so the same code works for 3 orders or 300,000: memory use does not grow with the number of rows (when the rows come from a database cursor rather than an array). sprintf() padding aligns the columns.
Permissions
Files created by PHP belong to the user running PHP (for example, www-data). Their permissions depend on the process's umask. To set them explicitly:
chmod($reportFile, 0640); // owner: read/write, group: read, others: none
Restrict permissions for files containing sensitive data, and store them outside the public web root.
Common Mistakes
Opening With w by Accident
Effect: Existing content is erased immediately.
Fix: Use a to append, c to keep content, or x to create only new files.
Ignoring Return Values
Effect: Failed writes (full disk, missing permissions) go unnoticed.
Fix: Check fopen(), fwrite(), and file_put_contents() for false.
Concurrent Writes Without Locks
Effect: Interleaved or lost data.
Fix: LOCK_EX for single writes; flock() around read-modify-write sequences.
Overwriting Important Files in Place
Effect: Crashes leave corrupted, half-written files.
Fix: Write to a temporary file and rename() it.
Writing Files Into the Public Folder
Effect: Reports, logs, or exports become downloadable by anyone.
Fix: Write to a storage/ folder outside the web root.
Best Practices
- Use
file_put_contents()for simple writes and streams for large or incremental output. - Choose file modes deliberately; prefer
xwhen overwriting would be a mistake. - Use
FILE_APPEND | LOCK_EXfor logs andflock()for read-modify-write operations. - Write critical files atomically with a temporary file and
rename(). - Check every return value and keep written files outside the web root.
Official reference: https://www.php.net/manual/en/function.fwrite.php