Skip to Content
Course content

220: Media Library Management in PHP

Click on the "Edit" button in the top corner of the screen to edit your slide content.

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() or random_bytes()) before saving it to an /assets folder.
  • The Database Link: Store both the unique filename and the original filename in the media table.
  • The Cleanup Logic: Create a separate delete.php script that takes a media ID via GET request. It must verify the file exists on the disk, delete the physical file using unlink(), and then remove the corresponding row from the database.
  • Validation: Ensure the script only allows jpg, png, and pdf files to be uploaded.
Rating
0 0

There are no comments for now.

to be the first to leave a comment.