PHP
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Functions
-
Section 4: Object-Oriented PHP
-
Section 5: Working with Data
-
Section 6: Modern PHP (PHP 8)
-
Section 7: Working with Files and Networking
-
Section 8: Common PHP Frameworks Overview
-
Section 9: Tooling and Ecosystem
-
Section 10: Practical Projects
-
Section 11: Interview Practice
-
Section 12: More Standard Library
-
Section 13: Data Structures and Algorithms in PHP
-
Section 14: More Practice Exercises
-
Section 15: More Security and Best Practices
-
Section 16: More Testing and Tooling
-
Section 17: WordPress-Style CMS Concepts
-
Section 18: Advanced OOP Practice
-
Section 19: More Web Fundamentals
-
Section 20: Database Practice
-
Section 21: PHP Manual: Array Functions
-
Section 22: PHP Manual: Date and Calendar Functions
-
Section 23: PHP Manual: Filesystem and Directory Functions
-
Section 24: PHP Manual: Filter and Var Handling
-
Section 25: PHP Manual: Math Functions
-
Section 26: PHP Manual: JSON and XML
-
Section 27: PHP Manual: Network and Stream Functions
-
Section 28: PHP Manual: Error and Exception Handling
-
Section 29: PHP Manual: Output Control and Misc
-
Section 30: PHP Manual: FTP, Zip, and Mail
-
Section 31: Modern PHP Frameworks Deep Dive
-
Section 32: PHP Design Patterns
-
Section 33: More Practice Exercises
-
Section 34: PHP Performance and Deployment
-
Section 35: More Interview Practice
-
Section 36: More PHP Standard Library
-
Section 37: PHP Concurrency and Async
-
Section 38: More Web Development Practice
-
Section 39: PHP Testing Deep Dive
-
Section 40: Composer and Package Development
-
Section 41: PHP Security Deep Dive
-
Section 42: More Practical Projects
-
Section 43: Legacy PHP Maintenance
-
Section 44: More Algorithm Practice
-
Section 45: Final Practice and Review
-
Section 46: PHP for E-Commerce Patterns
-
Section 47: PHP API Design Deep Dive
-
Section 48: PHP Caching Strategies
-
Section 49: PHP Queue and Background Jobs
-
Section 50: PHP Multi-Tenancy Patterns
-
Section 51: PHP Real-Time Features
-
Section 52: PHP CMS and Content Modeling
-
Section 53: PHP Internationalization
-
Section 54: More Framework-Specific Practice
-
Section 55: PHP Legacy Code Refactoring
-
Section 56: More Practice Projects Round 2
-
Section 57: PHP Command-Line Applications
-
Section 58: PHP and Microservices
-
Section 59: More Interview and Review Round 2
220: Media Library Management in PHP
You've likely already handled basic file uploads in earlier lessons, but there is a massive difference between "saving a file to a folder" and "managing a media library." A real media library needs to handle collisions, maintain database references, and ensure that when a record is deleted, the actual file doesn't just sit on your server forever taking up space.
I often see learners write code like this when they first try to build a gallery or a profile picture system:
// The "Naive" Upload Approach
$targetDir = "uploads/";
$fileName = $_FILES["media"]["name"];
$targetFilePath = $targetDir . $fileName;
if(move_uploaded_file($_FILES["media"]["tmp_name"], $targetFilePath)) {
$db->query("INSERT INTO media (file_path) VALUES ('$targetFilePath')");
echo "Upload successful!";
}
The disappearing file glitch
At first glance, this looks fine. It works for the first user. But here is the bug: what happens when User A uploads a photo called vacation.jpg, and then User B also uploads a photo called vacation.jpg?
Because the code uses the original filename provided by the browser, User B's file will overwrite User A's file on the disk. User A's database record still exists, but when they try to view their photo, they'll see User B's vacation instead. This is a classic "collision" bug. You can't trust the user to provide a unique filename.
Enforcing uniqueness with hashed identifiers
To fix this, we need to decouple the stored filename from the original filename. I prefer using bin2hex(random_bytes(16)) or uniqid() to generate a random string, then appending the original extension. This ensures that even if a thousand people upload image.jpg, every single one gets its own unique spot on the drive.
$targetDir = "uploads/";
$originalName = $_FILES["media"]["name"];
$extension = pathinfo($originalName, PATHINFO_EXTENSION);
// Generate a cryptographically secure random name
$safeName = bin2hex(random_bytes(16)) . '.' . $extension;
$targetFilePath = $targetDir . $safeName;
if(move_uploaded_file($_FILES["media"]["tmp_name"], $targetFilePath)) {
// We store BOTH the safe name (for the URL) and the original name (for the user's benefit)
$stmt = $db->prepare("INSERT INTO media (filename, original_name, mime_type) VALUES (?, ?, ?)");
$stmt->execute([$safeName, $originalName, $_FILES["media"]["type"]]);
}
By doing this, we've solved the collision problem. Now, the filesystem is an anonymous warehouse, and the database is the map that tells us which anonymous file belongs to which user.
Handling the "Orphaned File" problem
Another mistake I see is the "Ghost File" syndrome. You write a DELETE query to remove a media record from your database, but you forget to actually delete the file from the /uploads folder. Over six months, your server fills up with gigabytes of images that aren't linked to anything.
The logic should always be: Delete from disk first, then delete from database. If you delete the database record first and the script crashes before it hits the unlink() command, you've just created an orphan. You'll never find that file again because the "map" is gone.
// The correct way to remove media
$stmt = $db->prepare("SELECT filename FROM media WHERE id = ?");
$stmt->execute([$mediaId]);
$file = $stmt->fetch();
if ($file) {
$filePath = "uploads/" . $file['filename'];
// 1. Remove physical file first
if (file_exists($filePath)) {
unlink($filePath);
}
// 2. Then remove the database reference
$deleteStmt = $db->prepare("DELETE FROM media WHERE id = ?");
$deleteStmt->execute([$mediaId]);
}
Structuring for scalability
If you're building a library that will eventually hold thousands of files, don't put them all in one folder. Most filesystems slow down significantly when a single directory contains 10,000+ files. A common pro tip is to "shard" your directories. Instead of /uploads/file.jpg, you use the first few characters of the filename to create subfolders, like /uploads/a1/b2/file.jpg. It feels like overkill now, but your future self will thank you when the server doesn't choke during a backup.
📋 Practical Task
Exercise: Building a Robust Media Asset Manager
Your task is to create a PHP script that manages a simple media library. You will need a database table named media with the columns: id (INT AI), filename (VARCHAR), original_name (VARCHAR), and uploaded_at (DATETIME).
Requirements:
- The Upload Logic: Create a form that accepts a file upload. The script must rename the file to a unique string (using
uniqid()orrandom_bytes()) before saving it to an/assetsfolder. - The Database Link: Store both the unique filename and the original filename in the
mediatable. - The Cleanup Logic: Create a separate
delete.phpscript that takes a media ID via GET request. It must verify the file exists on the disk, delete the physical file usingunlink(), and then remove the corresponding row from the database. - Validation: Ensure the script only allows
jpg,png, andpdffiles to be uploaded.
There are no comments for now.