PHP Fundamentals

File Uploads

13 min Lesson 19 of 45

File Uploads

PHP makes it easy to handle file uploads from users. This is essential for applications that need profile pictures, document uploads, media files, and more.

Security Alert: File uploads are a major security risk if not handled properly. Always validate file type, size, and content. Never trust user input!

The $_FILES Superglobal

When files are uploaded, they are stored in the $_FILES superglobal array with the following structure:

<?php // $_FILES structure for a file input named "photo" $_FILES['photo']['name'] // Original filename $_FILES['photo']['type'] // MIME type (e.g., "image/jpeg") $_FILES['photo']['size'] // File size in bytes $_FILES['photo']['tmp_name'] // Temporary location on server $_FILES['photo']['error'] // Error code (0 = no error) ?>

Basic Upload Form

Forms that upload files must include the enctype attribute:

<!DOCTYPE html> <html> <head> <title>File Upload</title> </head> <body> <h1>Upload a File</h1> <!-- IMPORTANT: enctype="multipart/form-data" is required --> <form method="post" action="upload.php" enctype="multipart/form-data"> <label for="file">Choose file:</label> <input type="file" id="file" name="photo"> <button type="submit">Upload</button> </form> </body> </html>
Critical: Always include enctype="multipart/form-data" in forms with file uploads, or the files will not be sent.

Basic File Upload Handler

<?php // upload.php if ($_SERVER['REQUEST_METHOD'] === 'POST') { // Check if file was uploaded if (isset($_FILES['photo']) && $_FILES['photo']['error'] === 0) { // File details $filename = $_FILES['photo']['name']; $temp_path = $_FILES['photo']['tmp_name']; $file_size = $_FILES['photo']['size']; $file_type = $_FILES['photo']['type']; // Set upload directory $upload_dir = 'uploads/'; $destination = $upload_dir . $filename; // Move file from temp location to destination if (move_uploaded_file($temp_path, $destination)) { echo "File uploaded successfully: $filename"; } else { echo "Error uploading file."; } } else { echo "No file uploaded or an error occurred."; } } ?>

File Upload Error Codes

<?php // Check upload errors $error = $_FILES['photo']['error']; switch ($error) { case UPLOAD_ERR_OK: echo "No error"; break; case UPLOAD_ERR_INI_SIZE: echo "File exceeds upload_max_filesize in php.ini"; break; case UPLOAD_ERR_FORM_SIZE: echo "File exceeds MAX_FILE_SIZE in HTML form"; break; case UPLOAD_ERR_PARTIAL: echo "File was only partially uploaded"; break; case UPLOAD_ERR_NO_FILE: echo "No file was uploaded"; break; case UPLOAD_ERR_NO_TMP_DIR: echo "Missing temporary folder"; break; case UPLOAD_ERR_CANT_WRITE: echo "Failed to write file to disk"; break; default: echo "Unknown error"; } ?>

Secure File Upload with Validation

<?php // secure_upload.php $errors = []; $success = false; if ($_SERVER['REQUEST_METHOD'] === 'POST') { if (isset($_FILES['photo']) && $_FILES['photo']['error'] === 0) { $file = $_FILES['photo']; // Get file info $filename = $file['name']; $temp_path = $file['tmp_name']; $file_size = $file['size']; $file_type = $file['type']; // Get file extension $file_ext = strtolower(pathinfo($filename, PATHINFO_EXTENSION)); // 1. Validate file extension $allowed_extensions = ['jpg', 'jpeg', 'png', 'gif']; if (!in_array($file_ext, $allowed_extensions)) { $errors[] = "Invalid file type. Allowed: " . implode(', ', $allowed_extensions); } // 2. Validate MIME type $allowed_mime_types = ['image/jpeg', 'image/png', 'image/gif']; if (!in_array($file_type, $allowed_mime_types)) { $errors[] = "Invalid MIME type"; } // 3. Validate file size (max 5MB) $max_size = 5 * 1024 * 1024; // 5MB in bytes if ($file_size > $max_size) { $errors[] = "File too large. Maximum size: 5MB"; } // 4. Validate that it's actually an image $image_info = getimagesize($temp_path); if ($image_info === false) { $errors[] = "File is not a valid image"; } // If no errors, proceed with upload if (empty($errors)) { // Generate unique filename to prevent overwrites $new_filename = uniqid('img_', true) . '.' . $file_ext; // Set upload directory $upload_dir = 'uploads/'; // Create directory if it doesn't exist if (!is_dir($upload_dir)) { mkdir($upload_dir, 0755, true); } $destination = $upload_dir . $new_filename; // Move uploaded file if (move_uploaded_file($temp_path, $destination)) { $success = true; $uploaded_file = $new_filename; } else { $errors[] = "Failed to move uploaded file"; } } } else { $error_code = $_FILES['photo']['error']; if ($error_code === UPLOAD_ERR_NO_FILE) { $errors[] = "Please select a file"; } else { $errors[] = "Upload error code: $error_code"; } } } ?> <!DOCTYPE html> <html> <head> <title>Secure File Upload</title> <style> .error { color: red; } .success { color: green; } img { max-width: 300px; margin-top: 10px; } </style> </head> <body> <h1>Upload Image</h1> <?php if (!empty($errors)): ?> <div class="error"> <h3>Errors:</h3> <ul> <?php foreach ($errors as $error): ?> <li><?php echo $error; ?></li> <?php endforeach; ?> </ul> </div> <?php endif; ?> <?php if ($success): ?> <div class="success"> <h3>Upload Successful!</h3> <p>Filename: <?php echo htmlspecialchars($uploaded_file); ?></p> <img src="uploads/<?php echo htmlspecialchars($uploaded_file); ?>" alt="Uploaded image"> </div> <?php endif; ?> <form method="post" action="" enctype="multipart/form-data"> <label for="photo">Choose image (JPG, PNG, GIF - Max 5MB):</label> <input type="file" id="photo" name="photo" accept="image/*"> <button type="submit">Upload</button> </form> </body> </html>

Multiple File Uploads

<!-- HTML Form for Multiple Files --> <form method="post" action="upload_multiple.php" enctype="multipart/form-data"> <label>Choose multiple images:</label> <input type="file" name="photos[]" multiple accept="image/*"> <button type="submit">Upload All</button> </form>
<?php // upload_multiple.php if ($_SERVER['REQUEST_METHOD'] === 'POST') { if (isset($_FILES['photos'])) { $upload_dir = 'uploads/'; $uploaded_files = []; $errors = []; // Count number of files $file_count = count($_FILES['photos']['name']); // Loop through each file for ($i = 0; $i < $file_count; $i++) { // Check if file has no error if ($_FILES['photos']['error'][$i] === 0) { $filename = $_FILES['photos']['name'][$i]; $temp_path = $_FILES['photos']['tmp_name'][$i]; $file_size = $_FILES['photos']['size'][$i]; $file_ext = strtolower(pathinfo($filename, PATHINFO_EXTENSION)); $allowed = ['jpg', 'jpeg', 'png', 'gif']; // Validate if (!in_array($file_ext, $allowed)) { $errors[] = "$filename: Invalid file type"; continue; } if ($file_size > 5 * 1024 * 1024) { $errors[] = "$filename: File too large"; continue; } // Generate unique filename $new_filename = uniqid('img_', true) . '.' . $file_ext; $destination = $upload_dir . $new_filename; // Upload file if (move_uploaded_file($temp_path, $destination)) { $uploaded_files[] = $new_filename; } else { $errors[] = "$filename: Upload failed"; } } } echo "<h3>Uploaded " . count($uploaded_files) . " files</h3>"; foreach ($uploaded_files as $file) { echo "<p>$file</p>"; } if (!empty($errors)) { echo "<h3>Errors:</h3>"; foreach ($errors as $error) { echo "<p style='color:red;'>$error</p>"; } } } } ?>

File Upload Helper Functions

<?php // Helper function to format file size function formatFileSize($bytes) { if ($bytes >= 1073741824) { return number_format($bytes / 1073741824, 2) . ' GB'; } elseif ($bytes >= 1048576) { return number_format($bytes / 1048576, 2) . ' MB'; } elseif ($bytes >= 1024) { return number_format($bytes / 1024, 2) . ' KB'; } else { return $bytes . ' bytes'; } } // Helper function to generate safe filename function generateSafeFilename($original_name) { $ext = strtolower(pathinfo($original_name, PATHINFO_EXTENSION)); return uniqid('file_', true) . '.' . $ext; } // Helper function to validate image function validateImage($file) { $errors = []; // Check if file exists if (!isset($file) || $file['error'] !== 0) { $errors[] = "File upload error"; return $errors; } // Validate extension $ext = strtolower(pathinfo($file['name'], PATHINFO_EXTENSION)); $allowed = ['jpg', 'jpeg', 'png', 'gif', 'webp']; if (!in_array($ext, $allowed)) { $errors[] = "Invalid file extension"; } // Validate MIME type $allowed_mime = ['image/jpeg', 'image/png', 'image/gif', 'image/webp']; if (!in_array($file['type'], $allowed_mime)) { $errors[] = "Invalid MIME type"; } // Validate size (5MB) if ($file['size'] > 5 * 1024 * 1024) { $errors[] = "File too large (max 5MB)"; } // Validate it's a real image if (getimagesize($file['tmp_name']) === false) { $errors[] = "File is not a valid image"; } return $errors; } // Usage if ($_SERVER['REQUEST_METHOD'] === 'POST') { $errors = validateImage($_FILES['photo']); if (empty($errors)) { $new_filename = generateSafeFilename($_FILES['photo']['name']); $destination = 'uploads/' . $new_filename; if (move_uploaded_file($_FILES['photo']['tmp_name'], $destination)) { echo "Uploaded: $new_filename (" . formatFileSize($_FILES['photo']['size']) . ")"; } } else { foreach ($errors as $error) { echo "<p style='color:red;'>$error</p>"; } } } ?>

Image Manipulation (Resize)

<?php // Function to resize uploaded image function resizeImage($source, $destination, $max_width, $max_height) { // Get original dimensions list($orig_width, $orig_height, $type) = getimagesize($source); // Calculate new dimensions $ratio = min($max_width / $orig_width, $max_height / $orig_height); $new_width = intval($orig_width * $ratio); $new_height = intval($orig_height * $ratio); // Create image from source based on type switch ($type) { case IMAGETYPE_JPEG: $image = imagecreatefromjpeg($source); break; case IMAGETYPE_PNG: $image = imagecreatefrompng($source); break; case IMAGETYPE_GIF: $image = imagecreatefromgif($source); break; default: return false; } // Create new image $new_image = imagecreatetruecolor($new_width, $new_height); // Preserve transparency for PNG/GIF if ($type === IMAGETYPE_PNG || $type === IMAGETYPE_GIF) { imagealphablending($new_image, false); imagesavealpha($new_image, true); $transparent = imagecolorallocatealpha($new_image, 0, 0, 0, 127); imagefill($new_image, 0, 0, $transparent); } // Resize imagecopyresampled($new_image, $image, 0, 0, 0, 0, $new_width, $new_height, $orig_width, $orig_height); // Save resized image switch ($type) { case IMAGETYPE_JPEG: imagejpeg($new_image, $destination, 90); break; case IMAGETYPE_PNG: imagepng($new_image, $destination, 9); break; case IMAGETYPE_GIF: imagegif($new_image, $destination); break; } // Free memory imagedestroy($image); imagedestroy($new_image); return true; } // Usage after upload if (move_uploaded_file($_FILES['photo']['tmp_name'], $destination)) { // Create thumbnail $thumbnail = 'uploads/thumb_' . $new_filename; resizeImage($destination, $thumbnail, 300, 300); echo "Image uploaded and thumbnail created"; } ?>

Security Best Practices

File Upload Security Checklist:
  • Always validate file extension AND MIME type
  • Use getimagesize() to verify real images
  • Set maximum file size limits
  • Generate unique filenames (use uniqid())
  • Store uploads outside the web root when possible
  • Never execute uploaded files
  • Set proper file permissions (0644 for files)
  • Validate file content, not just extension
  • Use move_uploaded_file() (it validates the file)
  • Scan files with antivirus if possible
Common Vulnerabilities:
  • Trusting user-provided filename (rename files!)
  • Not validating MIME type (can be spoofed)
  • Allowing executable file types (.php, .exe)
  • Storing uploads in web-accessible directories without protection
  • Not limiting file size (DoS attack)

Practice Exercise

Task: Create a profile picture upload system with:

  1. Upload form accepting only images (JPG, PNG, GIF)
  2. Validate file type, MIME type, and size (max 2MB)
  3. Generate unique filename with timestamp
  4. Create two versions: original and thumbnail (150x150)
  5. Display both versions after upload
  6. Store files in "uploads/" directory
  7. Show file size in human-readable format

Bonus: Add ability to delete previous uploads and implement drag-and-drop upload interface.

Summary

In this lesson, you learned:

  • The $_FILES superglobal structure
  • Creating upload forms with proper enctype
  • Basic file upload handling with move_uploaded_file()
  • Upload error codes and handling
  • Secure file validation (extension, MIME type, size, content)
  • Generating unique filenames
  • Handling multiple file uploads
  • Helper functions for file operations
  • Image resizing and thumbnail creation
  • Critical security best practices

Next, we'll explore sessions and cookies in PHP!