File Handling
PHP provides a rich set of functions for working with files and directories. Whether you need to read configuration files, write logs, process CSV uploads, or manage a file-based cache, PHP makes it straightforward. Understanding file handling is essential for building applications that persist data, generate reports, or process uploaded content.
What You'll Learn
- Reading files with
file_get_contentsandfread - Writing files with
file_put_contentsandfwrite - Working with file handles:
fopen,fclose, and modes - Processing CSV files
- Directory operations: listing, creating, and removing
- File metadata and permissions
- Practical patterns for safe file operations
Quick File Operations
PHP's convenience functions let you read and write entire files in a single call:
<?php
// file_put_contents: write entire file at once
$data = "Hello, World!\nThis is line 2.\nThis is line 3.\n";
$bytesWritten = file_put_contents('/tmp/example.txt', $data);
echo "Wrote {$bytesWritten} bytes to file\n";
// file_get_contents: read entire file at once
$content = file_get_contents('/tmp/example.txt');
echo "File content:\n{$content}\n";
// Append to a file instead of overwriting
file_put_contents('/tmp/example.txt', "This is line 4.\n", FILE_APPEND);
echo "After append:\n" . file_get_contents('/tmp/example.txt') . "\n";
// file(): read file into an array of lines
$lines = file('/tmp/example.txt', FILE_IGNORE_NEW_LINES);
echo "Line count: " . count($lines) . "\n";
foreach ($lines as $index => $line) {
echo " Line {$index}: {$line}\n";
}
// Check if file exists before reading
$path = '/tmp/nonexistent.txt';
if (file_exists($path)) {
echo file_get_contents($path);
} else {
echo "\nFile does not exist: {$path}\n";
}
Use file_get_contents and file_put_contents for simple read/write operations where you do not need fine-grained control. The FILE_APPEND flag adds content to the end of a file instead of overwriting it.
Working with File Handles
For more control over reading and writing, use fopen to get a file handle, then operate on it with fread, fwrite, fgets, and fclose:
<?php
// File modes:
// 'r' - Read only (file must exist)
// 'w' - Write only (creates or truncates)
// 'a' - Append (creates if not exists)
// 'r+' - Read and write (file must exist)
// 'w+' - Read and write (creates or truncates)
// Write a structured log file
$logFile = fopen('/tmp/app.log', 'w');
if ($logFile === false) {
die("Cannot open log file\n");
}
$entries = [
['INFO', 'Application started'],
['DEBUG', 'Loading configuration'],
['INFO', 'Database connected'],
['WARNING', 'Cache miss for key: user_123'],
['ERROR', 'Failed to send email notification'],
];
foreach ($entries as [$level, $message]) {
$timestamp = date('Y-m-d H:i:s');
$line = "[{$timestamp}] [{$level}] {$message}\n";
fwrite($logFile, $line);
}
fclose($logFile);
echo "Log file written\n\n";
// Read the file line by line
$logFile = fopen('/tmp/app.log', 'r');
echo "Reading log entries:\n";
$lineNumber = 0;
while (($line = fgets($logFile)) !== false) {
$lineNumber++;
echo " {$lineNumber}: " . trim($line) . "\n";
}
fclose($logFile);
echo "\nFile size: " . filesize('/tmp/app.log') . " bytes\n";
Always close file handles with fclose when you are done. Reading line by line with fgets in a while loop is memory-efficient for large files because it does not load the entire file into memory.
CSV File Processing
CSV (Comma-Separated Values) is one of the most common data exchange formats. PHP has built-in functions for reading and writing CSV:
<?php
// Write CSV data
$csvFile = fopen('/tmp/employees.csv', 'w');
// Write header row
fputcsv($csvFile, ['Name', 'Department', 'Salary', 'Start Date']);
// Write data rows
$employees = [
['Alice Johnson', 'Engineering', 95000, '2022-01-15'],
['Bob Smith', 'Marketing', 75000, '2021-06-01'],
['Charlie Brown', 'Engineering', 105000, '2020-03-20'],
['Diana Prince', 'Design', 85000, '2023-02-10'],
['Eve Williams', 'Marketing', 78000, '2022-09-05'],
];
foreach ($employees as $employee) {
fputcsv($csvFile, $employee);
}
fclose($csvFile);
echo "CSV file written\n\n";
// Read and process CSV data
$csvFile = fopen('/tmp/employees.csv', 'r');
// Skip header row
$headers = fgetcsv($csvFile);
echo "Columns: " . implode(', ', $headers) . "\n\n";
// Read data rows
$totalSalary = 0;
$count = 0;
echo "Employees:\n";
while (($row = fgetcsv($csvFile)) !== false) {
[$name, $department, $salary, $startDate] = $row;
echo " {$name} | {$department} | \${$salary} | Started: {$startDate}\n";
$totalSalary += (float) $salary;
$count++;
}
fclose($csvFile);
$avgSalary = $totalSalary / $count;
echo "\nTotal employees: {$count}\n";
echo "Average salary: $" . number_format($avgSalary, 2) . "\n";
echo "Total payroll: $" . number_format($totalSalary, 2) . "\n";
The fputcsv and fgetcsv functions handle quoting and escaping automatically. This is much safer than manually splitting on commas, which breaks when fields contain commas or quotes.
Directory Operations
PHP provides functions for creating, listing, and removing directories:
<?php
// Create directories
$baseDir = '/tmp/php-demo';
if (!is_dir($baseDir)) {
mkdir($baseDir, 0755, true); // recursive: true creates parent dirs
echo "Created: {$baseDir}\n";
}
// Create subdirectories
$subdirs = ['uploads', 'cache', 'logs', 'cache/thumbnails'];
foreach ($subdirs as $subdir) {
$path = "{$baseDir}/{$subdir}";
if (!is_dir($path)) {
mkdir($path, 0755, true);
echo "Created: {$path}\n";
}
}
// Create some test files
file_put_contents("{$baseDir}/uploads/photo1.jpg", "fake image data 1");
file_put_contents("{$baseDir}/uploads/photo2.jpg", "fake image data 2");
file_put_contents("{$baseDir}/uploads/document.pdf", "fake pdf data");
file_put_contents("{$baseDir}/logs/app.log", "log entry 1\nlog entry 2\n");
// List directory contents
echo "\nDirectory listing of {$baseDir}:\n";
$items = scandir($baseDir);
foreach ($items as $item) {
if ($item === '.' || $item === '..') continue;
$fullPath = "{$baseDir}/{$item}";
$type = is_dir($fullPath) ? 'DIR ' : 'FILE';
echo " [{$type}] {$item}\n";
}
// List files matching a pattern
echo "\nJPG files in uploads:\n";
$jpgFiles = glob("{$baseDir}/uploads/*.jpg");
foreach ($jpgFiles as $file) {
$size = filesize($file);
echo " " . basename($file) . " ({$size} bytes)\n";
}
// File metadata
$file = "{$baseDir}/uploads/photo1.jpg";
echo "\nFile info for " . basename($file) . ":\n";
echo " Size: " . filesize($file) . " bytes\n";
echo " Modified: " . date('Y-m-d H:i:s', filemtime($file)) . "\n";
echo " Is readable: " . (is_readable($file) ? 'Yes' : 'No') . "\n";
echo " Is writable: " . (is_writable($file) ? 'Yes' : 'No') . "\n";
// Clean up: remove files and directories
unlink("{$baseDir}/uploads/photo1.jpg");
unlink("{$baseDir}/uploads/photo2.jpg");
unlink("{$baseDir}/uploads/document.pdf");
unlink("{$baseDir}/logs/app.log");
echo "\nCleaned up test files\n";
Use glob() to find files matching a pattern. The scandir() function lists all items in a directory. Always check if a directory exists with is_dir() before creating it, and use the recursive flag in mkdir() to create nested directories.
Safe File Operations
Combining error handling with file operations creates robust, production-ready code:
<?php
function safeWriteFile(string $path, string $content): bool {
// Write to a temporary file first, then rename (atomic operation)
$tempPath = $path . '.tmp.' . getmypid();
$dir = dirname($path);
if (!is_dir($dir)) {
if (!mkdir($dir, 0755, true)) {
echo "Error: Cannot create directory {$dir}\n";
return false;
}
}
$bytes = file_put_contents($tempPath, $content);
if ($bytes === false) {
echo "Error: Cannot write to {$tempPath}\n";
return false;
}
if (!rename($tempPath, $path)) {
unlink($tempPath);
echo "Error: Cannot rename temp file to {$path}\n";
return false;
}
return true;
}
function safeReadJson(string $path): array|null {
if (!file_exists($path)) {
echo "File not found: {$path}\n";
return null;
}
$content = file_get_contents($path);
if ($content === false) {
echo "Error reading: {$path}\n";
return null;
}
$data = json_decode($content, true);
if (json_last_error() !== JSON_ERROR_NONE) {
echo "JSON parse error: " . json_last_error_msg() . "\n";
return null;
}
return $data;
}
// Write JSON data safely
$config = [
'app_name' => 'My PHP App',
'version' => '1.0.0',
'debug' => true,
'database' => [
'host' => 'localhost',
'port' => 5432,
'name' => 'myapp',
],
];
$json = json_encode($config, JSON_PRETTY_PRINT);
$path = '/tmp/config.json';
if (safeWriteFile($path, $json)) {
echo "Configuration saved to {$path}\n";
}
// Read it back
$loaded = safeReadJson($path);
if ($loaded !== null) {
echo "App: {$loaded['app_name']} v{$loaded['version']}\n";
echo "Database: {$loaded['database']['host']}:{$loaded['database']['port']}\n";
echo "Debug mode: " . ($loaded['debug'] ? 'ON' : 'OFF') . "\n";
}
// Test error handling
$invalid = safeReadJson('/tmp/nonexistent.json');
echo "Invalid result: " . ($invalid === null ? 'null (correct)' : 'unexpected') . "\n";
The "write to temp then rename" pattern is an important technique. If the process crashes during writing, the original file remains intact. The rename() function is atomic on most filesystems, meaning it either completes fully or not at all.
Key Takeaways
- Use
file_get_contentsandfile_put_contentsfor simple one-shot reads and writes - Use
fopen/fclosewith handles for line-by-line processing and large files - PHP's
fgetcsvandfputcsvhandle CSV escaping and quoting automatically - Use
glob()for pattern-based file finding andscandir()for directory listing - Create directories recursively with
mkdir($path, 0755, true) - Always check
file_exists()andis_dir()before operations - Use the "write to temp, then rename" pattern for safe file writes
- Close file handles with
fcloseto free system resources
Next Steps
You now know how to read and write files, process CSV data, manage directories, and perform safe atomic file writes. Your PHP applications can handle both database and file-based storage.
There is one fundamental challenge in web development that databases and files alone cannot solve: HTTP is stateless. Every request your server receives is independent -- the server has no built-in way to remember who a user is between page loads. Sessions and cookies bridge that gap, letting you maintain state across requests. This is how login systems, shopping carts, and personalized experiences work, and understanding sessions is a prerequisite for authentication and framework features you will encounter later.
Continue to Sessions and Cookies -->
Pro Tip: For file uploads in web applications, never trust the client-provided filename. Use
pathinfo()to extract the extension, validate it against an allowlist, and generate a unique filename withuniqid()orrandom_bytes(). Store uploaded files outside the web root when possible, and always check$_FILES['upload']['error']before processing.