advanced Step 13 of 16

File Handling and Uploads

PHP Programming

File Handling and Uploads

File handling is essential for many web applications — from reading configuration files and processing CSV imports to handling user-uploaded images and generating PDF reports. PHP provides comprehensive file system functions for reading, writing, and manipulating files, along with a built-in mechanism for handling multipart file uploads from HTML forms. Understanding file handling security is particularly important because improper handling of file uploads is a common source of vulnerabilities in web applications.

Reading and Writing Files

<?php
// Read entire file
$content = file_get_contents('data.txt');

// Write entire file
file_put_contents('output.txt', "Hello, World!
");

// Append to file
file_put_contents('log.txt', "[" . date('Y-m-d H:i:s') . "] Event occurred
", FILE_APPEND);

// Read line by line (memory efficient for large files)
$handle = fopen('large-file.csv', 'r');
if ($handle) {
    while (($line = fgets($handle)) !== false) {
        echo trim($line) . "
";
    }
    fclose($handle);
}

// Read CSV file
$handle = fopen('users.csv', 'r');
$headers = fgetcsv($handle);
$users = [];
while (($row = fgetcsv($handle)) !== false) {
    $users[] = array_combine($headers, $row);
}
fclose($handle);

// Write CSV file
$handle = fopen('export.csv', 'w');
fputcsv($handle, ['Name', 'Email', 'Age']);
foreach ($users as $user) {
    fputcsv($handle, [$user['name'], $user['email'], $user['age']]);
}
fclose($handle);

// Read JSON configuration
$config = json_decode(file_get_contents('config.json'), true);
echo $config['database']['host'];

// File system operations
echo file_exists('data.txt') ? 'Exists' : 'Not found';
echo filesize('data.txt');           // Size in bytes
echo filemtime('data.txt');          // Last modified timestamp
copy('source.txt', 'backup.txt');
rename('old.txt', 'new.txt');
unlink('temp.txt');                  // Delete file

// Directory operations
mkdir('uploads/2024', 0755, true);   // Create nested directories
$files = scandir('uploads');
rmdir('empty-dir');
?>

File Uploads

<!-- Upload form -->
<form method="POST" action="upload.php" enctype="multipart/form-data">
    <input type="file" name="document" accept=".pdf,.doc,.docx" required>
    <button type="submit">Upload</button>
</form>

<?php
// upload.php — secure file upload handler
function handleUpload(string $fieldName, string $uploadDir): array {
    if (!isset($_FILES[$fieldName]) || $_FILES[$fieldName]['error'] !== UPLOAD_ERR_OK) {
        $errorMessages = [
            UPLOAD_ERR_INI_SIZE => 'File exceeds server limit',
            UPLOAD_ERR_FORM_SIZE => 'File exceeds form limit',
            UPLOAD_ERR_PARTIAL => 'File partially uploaded',
            UPLOAD_ERR_NO_FILE => 'No file selected',
        ];
        $code = $_FILES[$fieldName]['error'] ?? UPLOAD_ERR_NO_FILE;
        throw new RuntimeException($errorMessages[$code] ?? 'Upload failed');
    }

    $file = $_FILES[$fieldName];
    $maxSize = 5 * 1024 * 1024;  // 5 MB
    if ($file['size'] > $maxSize) {
        throw new RuntimeException('File too large (max 5 MB)');
    }

    // Validate MIME type (not just extension!)
    $allowedTypes = ['application/pdf', 'image/jpeg', 'image/png', 'image/webp'];
    $finfo = new finfo(FILEINFO_MIME_TYPE);
    $mimeType = $finfo->file($file['tmp_name']);
    if (!in_array($mimeType, $allowedTypes)) {
        throw new RuntimeException("File type not allowed: $mimeType");
    }

    // Generate safe filename
    $extension = pathinfo($file['name'], PATHINFO_EXTENSION);
    $safeName = bin2hex(random_bytes(16)) . '.' . strtolower($extension);

    // Create upload directory if needed
    if (!is_dir($uploadDir)) {
        mkdir($uploadDir, 0755, true);
    }

    $destination = $uploadDir . '/' . $safeName;
    if (!move_uploaded_file($file['tmp_name'], $destination)) {
        throw new RuntimeException('Failed to save file');
    }

    return [
        'original_name' => $file['name'],
        'saved_name' => $safeName,
        'mime_type' => $mimeType,
        'size' => $file['size'],
        'path' => $destination
    ];
}

try {
    $result = handleUpload('document', __DIR__ . '/../storage/uploads');
    echo json_encode(['success' => true, 'file' => $result]);
} catch (RuntimeException $e) {
    http_response_code(400);
    echo json_encode(['error' => $e->getMessage()]);
}
?>
Pro tip: Never trust the file extension from the user — always validate the MIME type using finfo. Generate random filenames to prevent overwriting and path traversal attacks. Never place the upload directory inside the web root unless you need to serve the files directly, and if you do, use a separate domain or configure the web server to prevent script execution in the upload directory.

Key Takeaways

  • Use file_get_contents()/file_put_contents() for simple reads and writes; fopen()/fgets() for large files.
  • Always validate uploaded files: check MIME type (not just extension), enforce size limits, and generate random filenames.
  • Use move_uploaded_file() to safely move uploaded files — it verifies the file was actually uploaded via HTTP.
  • Read and write CSV files with fgetcsv() and fputcsv() for proper quoting and escaping.
  • Never store uploads in the web root without proper security measures; validate MIME types with finfo.