Lesson 3.4: File Handling and Uploads
Files are everywhere in web applications. User avatars, document uploads, configuration files, log files, data exports, backup files—modern websites constantly read, write, and manage files. Whether you're building a simple blog that stores posts in text files or a complex document management system, understanding file operations is essential for creating useful, dynamic applications.
Think about the applications you use daily. Your email client downloads attachments, social media platforms handle photo uploads, and cloud storage services manage millions of files. Behind every file operation is code that validates, processes, stores, and retrieves files safely and efficiently.
File handling also presents unique security challenges. Malicious users can upload dangerous files, consume server storage, or exploit file processing vulnerabilities. Learning proper file management means building applications that are both functional and secure—protecting your server while providing users with the file features they expect.
The difference between amateur and professional applications often shows in file handling. Amateur apps crash when files are too large, store files insecurely, or fail to validate file types. Professional applications handle edge cases gracefully, implement proper security measures, and provide smooth user experiences even when things go wrong.
Understanding PHP File Operations
PHP provides a comprehensive set of functions for working with files and directories. These functions let you read content, write data, check file properties, and manage entire directory structures. Understanding these basics forms the foundation for more complex file operations.
Reading File Contents
The simplest file operations involve reading existing files. This might be loading configuration settings, reading template files, or processing uploaded documents.
<?php
// Reading entire file at once
$configContent = file_get_contents('config.txt');
if ($configContent !== false) {
echo "Config loaded: " . strlen($configContent) . " bytes\n";
} else {
echo "Failed to load config file\n";
}
// Reading file line by line (memory efficient for large files)
$logFile = fopen('app.log', 'r');
if ($logFile) {
while (($line = fgets($logFile)) !== false) {
echo "Log entry: " . trim($line) . "\n";
}
fclose($logFile);
}
?>
The file_get_contents()
function is perfect for small files where you need all content at once. For large files, using fopen()
and fgets()
prevents memory issues by processing one line at a time.
Here's how to safely check if a file exists before reading it:
<?php
$filename = 'user-data.txt';
if (file_exists($filename) && is_readable($filename)) {
$userData = file_get_contents($filename);
echo "User data loaded successfully\n";
} else {
echo "User data file not found or not readable\n";
// Create default data or show error to user
}
?>
Writing and Creating Files
Writing files lets you store user data, generate reports, create logs, and save application state. Proper file writing includes error handling and permission checking.
<?php
$logMessage = date('Y-m-d H:i:s') . " - User logged in\n";
// Append to existing file
if (file_put_contents('app.log', $logMessage, FILE_APPEND | LOCK_EX)) {
echo "Log entry added\n";
} else {
echo "Failed to write log entry\n";
}
// Create new file with content
$reportData = "Sales Report\n" .
"Generated: " . date('Y-m-d') . "\n" .
"Total Sales: $15,234\n";
if (file_put_contents('daily-report.txt', $reportData)) {
echo "Report generated successfully\n";
}
?>
The FILE_APPEND
flag adds content to the end of existing files instead of overwriting them. The LOCK_EX
flag prevents other processes from writing to the file simultaneously, which is crucial for log files and shared data.
For more control over file writing, use the traditional approach:
<?php
$csvData = [
['Name', 'Email', 'Age'],
['John Smith', '[email protected]', '30'],
['Jane Doe', '[email protected]', '25']
];
$file = fopen('users.csv', 'w');
if ($file) {
foreach ($csvData as $row) {
fputcsv($file, $row);
}
fclose($file);
echo "CSV file created successfully\n";
}
?>
Working with File Information
Before processing files, you often need to check their properties—size, type, modification date, and permissions. This information helps you make decisions about how to handle each file.
<?php
$filename = 'uploaded-document.pdf';
if (file_exists($filename)) {
$fileSize = filesize($filename);
$fileType = mime_content_type($filename);
$lastModified = filemtime($filename);
echo "File: $filename\n";
echo "Size: " . number_format($fileSize / 1024, 2) . " KB\n";
echo "Type: $fileType\n";
echo "Modified: " . date('Y-m-d H:i:s', $lastModified) . "\n";
// Check if file is writable
if (is_writable($filename)) {
echo "File can be modified\n";
} else {
echo "File is read-only\n";
}
}
?>
Getting file extensions helps with type validation:
<?php
function getFileExtension($filename) {
return strtolower(pathinfo($filename, PATHINFO_EXTENSION));
}
$uploadedFile = 'document.PDF';
$extension = getFileExtension($uploadedFile);
$allowedExtensions = ['pdf', 'doc', 'docx', 'txt'];
if (in_array($extension, $allowedExtensions)) {
echo "Valid document type: $extension\n";
} else {
echo "Invalid file type. Please upload a document.\n";
}
?>
Handling File Uploads Properly
File uploads are common in web applications but require careful handling to ensure security and functionality. Every aspect—from validation to storage—needs attention to detail.
Basic Upload Processing
Understanding the $_FILES
superglobal is essential for processing uploads. Each uploaded file creates an array with specific information about the upload.
<?php
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_FILES['document'])) {
$file = $_FILES['document'];
echo "Upload Information:\n";
echo "Original name: " . $file['name'] . "\n";
echo "Temporary location: " . $file['tmp_name'] . "\n";
echo "File size: " . number_format($file['size'] / 1024, 2) . " KB\n";
echo "MIME type: " . $file['type'] . "\n";
echo "Error code: " . $file['error'] . "\n";
// Check for upload errors
if ($file['error'] === UPLOAD_ERR_OK) {
echo "File uploaded successfully to temporary location\n";
} else {
echo "Upload failed with error code: " . $file['error'] . "\n";
}
}
?>
The temporary file gets automatically deleted unless you move it to a permanent location using move_uploaded_file()
.
Comprehensive Upload Validation
Proper validation checks multiple aspects of uploaded files to ensure they meet your requirements and don't pose security risks.
<?php
function validateUpload($file) {
$errors = [];
// Check if file was actually uploaded
if ($file['error'] !== UPLOAD_ERR_OK) {
switch ($file['error']) {
case UPLOAD_ERR_INI_SIZE:
$errors[] = "File too large (server limit exceeded)";
break;
case UPLOAD_ERR_FORM_SIZE:
$errors[] = "File too large (form limit exceeded)";
break;
case UPLOAD_ERR_PARTIAL:
$errors[] = "File upload was interrupted";
break;
case UPLOAD_ERR_NO_FILE:
$errors[] = "No file was uploaded";
break;
default:
$errors[] = "Upload failed";
}
return $errors;
}
// Check file size (2MB limit)
$maxSize = 2 * 1024 * 1024;
if ($file['size'] > $maxSize) {
$errors[] = "File too large (max 2MB allowed)";
}
// Validate file extension
$allowedExtensions = ['jpg', 'jpeg', 'png', 'gif', 'pdf', 'doc', 'docx'];
$extension = strtolower(pathinfo($file['name'], PATHINFO_EXTENSION));
if (!in_array($extension, $allowedExtensions)) {
$errors[] = "File type not allowed";
}
// Validate MIME type (more secure than extension checking)
$allowedMimeTypes = [
'image/jpeg', 'image/png', 'image/gif',
'application/pdf', 'application/msword',
'application/vnd.openxmlformats-officedocument.wordprocessingml.document'
];
if (!in_array($file['type'], $allowedMimeTypes)) {
$errors[] = "Invalid file format";
}
return $errors;
}
?>
Using this validation function:
<?php
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_FILES['upload'])) {
$errors = validateUpload($_FILES['upload']);
if (empty($errors)) {
echo "File validation passed!\n";
// Proceed with file processing
} else {
echo "Upload errors:\n";
foreach ($errors as $error) {
echo "• $error\n";
}
}
}
?>
Secure File Storage
Simply validating uploads isn't enough—you must store files securely to prevent various attack vectors. This involves choosing safe locations, generating secure filenames, and setting proper permissions.
<?php
function secureFileUpload($file, $uploadDir = 'uploads/') {
// Validate the upload first
$errors = validateUpload($file);
if (!empty($errors)) {
return ['success' => false, 'errors' => $errors];
}
// Create upload directory if it doesn't exist
if (!is_dir($uploadDir)) {
mkdir($uploadDir, 0755, true);
}
// Generate secure filename
$extension = strtolower(pathinfo($file['name'], PATHINFO_EXTENSION));
$filename = uniqid('file_', true) . '.' . $extension;
$fullPath = $uploadDir . $filename;
// Move uploaded file to permanent location
if (move_uploaded_file($file['tmp_name'], $fullPath)) {
return [
'success' => true,
'filename' => $filename,
'path' => $fullPath,
'size' => $file['size']
];
} else {
return ['success' => false, 'errors' => ['Failed to save file']];
}
}
?>
Using the secure upload function:
<?php
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_FILES['document'])) {
$result = secureFileUpload($_FILES['document']);
if ($result['success']) {
echo "File uploaded successfully!\n";
echo "Saved as: " . $result['filename'] . "\n";
echo "Size: " . number_format($result['size'] / 1024, 2) . " KB\n";
} else {
echo "Upload failed:\n";
foreach ($result['errors'] as $error) {
echo "• $error\n";
}
}
}
?>
Working with Directories
Managing directories is essential for organizing uploaded files, creating backup structures, and maintaining clean file systems. PHP provides powerful functions for directory operations.
<?php
$baseDir = 'user-files/';
$userDir = $baseDir . 'user-123/';
// Create directory structure
if (!is_dir($userDir)) {
if (mkdir($userDir, 0755, true)) {
echo "User directory created: $userDir\n";
} else {
echo "Failed to create directory\n";
}
}
// List directory contents
if (is_dir($userDir)) {
$files = scandir($userDir);
foreach ($files as $file) {
if ($file !== '.' && $file !== '..') {
$filePath = $userDir . $file;
$fileSize = filesize($filePath);
echo "File: $file (" . number_format($fileSize / 1024, 2) . " KB)\n";
}
}
}
?>
Here's a more sophisticated directory listing function:
<?php
function getDirectoryListing($directory) {
$files = [];
if (!is_dir($directory)) {
return $files;
}
$items = scandir($directory);
foreach ($items as $item) {
if ($item === '.' || $item === '..') {
continue;
}
$fullPath = $directory . '/' . $item;
$files[] = [
'name' => $item,
'size' => filesize($fullPath),
'type' => is_dir($fullPath) ? 'directory' : 'file',
'modified' => filemtime($fullPath)
];
}
return $files;
}
// Usage
$listing = getDirectoryListing('uploads/');
foreach ($listing as $item) {
$type = $item['type'] === 'directory' ? '[DIR]' : '[FILE]';
$size = $item['type'] === 'file' ?
number_format($item['size'] / 1024, 2) . ' KB' : '';
echo "$type {$item['name']} $size\n";
}
?>
Building a File Manager
Let's create a practical file manager that demonstrates professional file handling techniques. This example shows how to combine upload, validation, storage, and listing functionality.
<?php
class SimpleFileManager {
private $uploadDir;
private $maxFileSize;
private $allowedTypes;
public function __construct($uploadDir = 'files/') {
$this->uploadDir = rtrim($uploadDir, '/') . '/';
$this->maxFileSize = 5 * 1024 * 1024; // 5MB
$this->allowedTypes = ['pdf', 'doc', 'docx', 'txt', 'jpg', 'png'];
// Create upload directory if needed
if (!is_dir($this->uploadDir)) {
mkdir($this->uploadDir, 0755, true);
}
}
public function uploadFile($file) {
if ($file['error'] !== UPLOAD_ERR_OK) {
return ['success' => false, 'message' => 'Upload failed'];
}
if ($file['size'] > $this->maxFileSize) {
return ['success' => false, 'message' => 'File too large'];
}
$extension = strtolower(pathinfo($file['name'], PATHINFO_EXTENSION));
if (!in_array($extension, $this->allowedTypes)) {
return ['success' => false, 'message' => 'File type not allowed'];
}
$filename = time() . '_' . preg_replace('/[^a-zA-Z0-9._-]/', '', $file['name']);
$fullPath = $this->uploadDir . $filename;
if (move_uploaded_file($file['tmp_name'], $fullPath)) {
return [
'success' => true,
'message' => 'File uploaded successfully',
'filename' => $filename
];
}
return ['success' => false, 'message' => 'Failed to save file'];
}
public function listFiles() {
$files = [];
$items = scandir($this->uploadDir);
foreach ($items as $item) {
if ($item === '.' || $item === '..') continue;
$fullPath = $this->uploadDir . $item;
if (is_file($fullPath)) {
$files[] = [
'name' => $item,
'size' => filesize($fullPath),
'modified' => filemtime($fullPath)
];
}
}
return $files;
}
public function deleteFile($filename) {
$fullPath = $this->uploadDir . basename($filename); // basename prevents directory traversal
if (file_exists($fullPath) && is_file($fullPath)) {
if (unlink($fullPath)) {
return ['success' => true, 'message' => 'File deleted'];
}
}
return ['success' => false, 'message' => 'Failed to delete file'];
}
}
?>
Using the file manager:
<?php
$fileManager = new SimpleFileManager();
// Handle file upload
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_FILES['file'])) {
$result = $fileManager->uploadFile($_FILES['file']);
echo $result['message'] . "\n";
}
// List all files
$files = $fileManager->listFiles();
echo "Files in system:\n";
foreach ($files as $file) {
$size = number_format($file['size'] / 1024, 2);
$date = date('Y-m-d H:i', $file['modified']);
echo "• {$file['name']} ({$size} KB, modified: $date)\n";
}
?>
Security Considerations
File handling presents numerous security risks that you must address to protect your application and server. Understanding these risks helps you implement appropriate safeguards.
Preventing Directory Traversal
Directory traversal attacks try to access files outside your intended directory structure using paths like ../../../etc/passwd
.
<?php
function safeFilePath($filename, $baseDir = 'uploads/') {
// Remove any directory traversal attempts
$filename = basename($filename);
// Remove any dangerous characters
$filename = preg_replace('/[^a-zA-Z0-9._-]/', '', $filename);
// Ensure filename isn't empty after cleaning
if (empty($filename)) {
$filename = 'file_' . uniqid();
}
return $baseDir . $filename;
}
// Example of safe file access
$requestedFile = $_GET['file'] ?? '';
$safePath = safeFilePath($requestedFile);
if (file_exists($safePath)) {
echo "Accessing safe file: $safePath\n";
} else {
echo "File not found\n";
}
?>
File Type Validation
Never trust file extensions or MIME types alone—they can be easily spoofed. Use multiple validation methods for critical applications.
<?php
function validateImageFile($file) {
// Check MIME type
$allowedMimes = ['image/jpeg', 'image/png', 'image/gif'];
if (!in_array($file['type'], $allowedMimes)) {
return false;
}
// Check file signature (magic bytes)
$handle = fopen($file['tmp_name'], 'r');
$bytes = fread($handle, 4);
fclose($handle);
// JPEG signature
if (substr($bytes, 0, 2) === "\xFF\xD8") {
return true;
}
// PNG signature
if ($bytes === "\x89PNG") {
return true;
}
// GIF signature
if (substr($bytes, 0, 3) === "GIF") {
return true;
}
return false;
}
?>
Error Handling and Logging
Proper error handling in file operations prevents crashes and provides useful debugging information while keeping sensitive details away from users.
<?php
function safeFileOperation($operation, $filename) {
try {
switch ($operation) {
case 'read':
if (!file_exists($filename)) {
throw new Exception("File not found: $filename");
}
return file_get_contents($filename);
case 'write':
$result = file_put_contents($filename, "Test content");
if ($result === false) {
throw new Exception("Failed to write file: $filename");
}
return true;
default:
throw new Exception("Unknown operation: $operation");
}
} catch (Exception $e) {
// Log detailed error for developers
error_log("File operation failed: " . $e->getMessage());
// Return user-friendly message
return "File operation failed. Please try again.";
}
}
// Usage
$result = safeFileOperation('read', 'config.txt');
if (is_string($result) && strpos($result, 'failed') === false) {
echo "File content loaded successfully\n";
} else {
echo $result; // Error message
}
?>
Performance Optimization
File operations can be expensive, especially with large files or high traffic. These optimization techniques help maintain good performance.
<?php
// Reading large files efficiently
function processLargeFile($filename, $chunkSize = 8192) {
$handle = fopen($filename, 'r');
if (!$handle) {
return false;
}
$lineCount = 0;
while (!feof($handle)) {
$chunk = fread($handle, $chunkSize);
$lineCount += substr_count($chunk, "\n");
}
fclose($handle);
return $lineCount;
}
// Caching file information
function getCachedFileInfo($filename) {
static $cache = [];
if (!isset($cache[$filename])) {
$cache[$filename] = [
'size' => filesize($filename),
'modified' => filemtime($filename),
'type' => mime_content_type($filename)
];
}
return $cache[$filename];
}
?>
Key Takeaways
File handling is a critical skill for web development, touching everything from user uploads to data processing and system logs. Always validate file types and sizes, use secure storage practices, and implement proper error handling to create robust applications.
Security should be your top priority when handling files. Validate uploads thoroughly, prevent directory traversal attacks, and never trust user-provided filenames or paths. Remember that files can be vectors for attacks, so treat them with appropriate caution.
Performance matters when dealing with large files or high upload volumes. Use streaming techniques for large files, implement proper caching, and consider the impact of file operations on your server resources.
Your string manipulation and form handling skills from previous lessons are essential here—file operations often involve parsing filenames, validating paths, and processing form uploads. Combine these skills to create comprehensive file management systems that users can rely on.
In the next lesson, we'll explore JSON and data serialization, which often works hand-in-hand with file handling to store and exchange structured data in modern web applications.