PHP File Create/Write

Su Subhalakshmi Chandran Updated 05 Nov 2026
8 min read ·Lesson 245 of 248

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
BehaviorDetail
File does not existIt is created
File existsIts contents are replaced
Return valueNumber of bytes written, or false on failure
FILE_APPEND flagAdds to the end instead of replacing
LOCK_EX flagLocks the file exclusively while writing
Array as $dataElements 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

  1. fopen($file, 'w') creates the file (or empties an existing one) and returns a handle.
  2. 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.
  3. fclose() flushes any buffered data and closes the file. Until a file is closed (or flushed with fflush()), some data may still be in memory.

fputs() is an alias of fwrite().


File Modes for Writing

ModeOpens ForIf the File ExistsIf MissingPosition
wWritingEmptiedCreatedStart
aWriting (append)KeptCreatedEnd (all writes go to the end)
xWritingFailsCreatedStart
cWritingKept (not emptied)CreatedStart
w+, a+, x+, c+Reading and writingAs aboveAs aboveAs 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: w empties a file immediately when it is opened, even if nothing is written afterwards. Opening an important file with w by 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

  1. tempnam(dirname($path), 'tmp_') creates a uniquely named empty file in the same directory, which ensures the final rename() happens within one filesystem.
  2. The complete content is written to the temporary file.
  3. 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

  1. Mode c+ opens the file for reading and writing, creating it if needed, without emptying it — the current value must be read first.
  2. 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.
  3. The current value is read (an empty new file gives 0), incremented, and written back after truncating the file.
  4. fflush() ensures the data is written before the lock is released in finally.

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 x when overwriting would be a mistake.
  • Use FILE_APPEND | LOCK_EX for logs and flock() 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

0 Comments

Reviewed before they appear

No comments yet.

PHP
PHP Home PHP Switch PHP Match Alternative Syntax PHP Loops While Loop Do While Loop For Loop Foreach Loop Nested Loops Break Statement PHP Intro PHP Install PHP Built-in Web Server PHP Syntax PHP Tags PHP and HTML PHP Comments PHP Multiline Comments PHP Variables Variables Scope PHP Casting Logical Operators Null Coalescing Operator Spaceship Operator Operator Precedence Continue Statement Modify Strings PHP If...Else...Elseif PHP If Modify Strings PHP If Operators PHP Functions Concatenate Strings PHP If...Else User-defined Functions Slicing Strings Function Arguments PHP Shorthand if Escape Characters Default Argument Values PHP Nested if Pass by Reference String Interpolation Named Arguments PHP File Upload Variadic Functions Return Values Search in Strings Multibyte Strings Type Declarations in Functions PHP Numbers PHP Math PHP Floats PHP Random Numbers PHP Constants PHP Properties PHP Methods PHP Constructor Constructor Property Promotion PHP Cookies PHP Destructor PHP Access Modifiers Readonly Properties PHP Sessions PHP Inheritance Method Overriding PHP Filters The final Keyword CSRF Protection PHP Filters Advanced Session Security Secure Cookies PHP Magic Constants File Upload Security PHP Callback Functions PHP Operators Filesystem Security PHP JSON Arithmetic Operators Error Reporting in Production Hiding PHP Assignment Operators PHP Configuration (php.ini) Comparison Operators PHP Command Line (CLI) PHP Exceptions Increment / Decrement Operators Migrating to PHP 8 PHP Form Validation PHP Form Required PHP Form URL/E-mail Error Handling Form Input Sanitizing Escaping Output (htmlspecialchars) Custom Error Handler Checkbox, Radio and Select Inputs Sticky Forms PHP Form Complete PHP Date and Time Date Formatting Error Logging HTTP Headers and Redirects Output Buffering Sending Email PHP cURL Consuming REST APIs PHP Generators AJAX PHP AJAX Database AJAX XML AJAX Live Search AJAX Poll AJAX with JSON Security Introduction Password Hashing SQL Injection Prevention XSS Prevention Garbage Collection Project: Simple Blog PHP What is OOP PHP Classes/Objects The $this Keyword Access Array Items Update Array Items Add Array Items Remove Array Items Sorting Arrays Multidimensional Arrays Array Destructuring Array Spread Operator Search in Arrays Merge Arrays PHP Abstract Classes PHP Interfaces Polymorphism PHP Traits PHP Static Methods PHP Static Properties Late Static Binding Magic Methods Object Cloning MySQL Database MySQL Connect MySQL Create DB MySQL Create Table MySQL Insert Data MySQL Get Last ID MySQL Insert Multiple MySQL Prepared MySQL Select Data MySQL Where Debugging PHP Project: Image Upload Gallery $_SESSION $_ENV PHP RegEx RegEx Patterns Heredoc and Nowdoc RegEx Modifiers preg_match preg_replace preg_split PHP RegEx Functions Nullable Types Mixed and Never Types Nullsafe Operator PHP Attributes PHP Fibers Readonly Classes Property Hooks (PHP 8.4) Asymmetric Visibility (PHP 8.4) Reflection API SPL Data Structures Composer PHP Form Handling PDO Transactions PDO Error Handling PHP XML Parsers PHP SimpleXML Parser PHP SimpleXML - Get PHP XML Expat Parser PHP DOM Parser PHP XMLReader PHP XMLWriter AJAX Intro PHP Class Constants Encryption with Sodium Recursive Functions Variable Functions Anonymous Functions Closures Arrow Functions First-class Callable Syntax PHP Arrays Indexed Arrays Associative Arrays Create Arrays Variables Scope Static Variables Variable Variables PHP Echo / Print PHP printf / sprintf PHP Data Types PHP Booleans PHP NULL PHP Strings String Functions array_map / array_filter / array_reduce Array Functions PHP Superglobals $GLOBALS What Is `$_SERVER` ? $_REQUEST $_POST $_GET $_FILES $_COOKIE PHP Type Juggling MySQL Order By MySQL Delete Data MySQL Update Data MySQL Limit Data MySQL Joins with PHP Pagination with PHP PDO Introduction PDO Connect PDO Prepared Statements PDO Fetch Modes PSR-4 Autoloading Installing Extensions (PECL / PIE) OPcache & Performance PHP-FPM Project: Contact Form with Email Project: Login & Registration System Project: CRUD App with MySQL Project: Shopping Cart with Sessions Project: REST API with PHP DateTime Class Timezones PHP Include Include vs Require PHP File Handling PHP File Open/Read PHP File Create/Write File Delete / Rename / Copy PHP Directories PHP CSV Files
Ask about this post
AI Ask about this post

Ask questions about PHP File Create/Write and get answers drawn from it.

Signed-in readers only.