
🚀 Mehrere dateien mit HTML und PHP hochladen
Ein Benutzer muss fünf Bilder hochladen, das Formular akzeptiert aber nur eines. Oder ein Kunde wünscht einen Stapel-Upload von Dokumenten mit einem Titel zu jeder Datei. Das ist keine Fantasie: Es handelt sich um eine Standardaufgabe, die PHP in dreißig Zeilen löst.
Das Problem: Die meisten Tutorials zeigen nur Einzeldatei-Uploads. Multi-Uploads funktionieren anders: ein Array von Dateien statt einer einzelnen, Zuordnung zu zusätzlichen Feldern, Schutz vor Überschreiben. Nachfolgend eine funktionierende Implementierung von Grund auf bis zum einsatzbereiten Snippet.
💡 Kurzüberblick:
- Erstellen Sie ein HTML-Formular mit
enctype="multipart/form-data"und einemfile-Feld mit dem Attributmultiple. - Verarbeiten Sie das
$_FILES-Array in PHP: Durchlaufen Sie die Indizes und nutzen Siemove_uploaded_file()für jede Datei. - Fügen Sie vor dem Speichern auf der Festplatte eine Prüfung von Dateiendung und Größe hinzu.
- Verwenden Sie für die Verknüpfung „Datei + Titel" parallele Arrays
title[]undfileUpload[].
Eine kurze Video-Anleitung zum gleichen Thema, vom HTML-Formular bis zum Speichern der Dateien in einer Datenbank:
Schritt 1: HTML-Formular für den Upload mehrerer Dateien
Erstellen Sie zuerst das Formular. Entscheidende Punkte: das Attribut enctype mit dem Wert multipart/form-data und ein Eingabefeld mit multiple. Ohne enctype empfängt der Server schlicht keine Dateien; das ist kein PHP-Fehler, sondern die HTTP-Spezifikation.
Benennen Sie das Feld als Array: fileUpload[]. Die eckigen Klammern am Ende teilen PHP mit, dass der Server eine Liste empfängt, keine einzelne Datei.
1 <form action="" method="post" enctype="multipart/form-data"> 2 <label>Select files:</label> 3 <input type="file" name="fileUpload[]" multiple> 4 <input type="submit" name="Submit" value="Upload"> 5 </form>
Das obige Formular erlaubt es, im Dateidialog beliebig viele Dateien auf einmal auszuwählen. Der Browser verpackt sie in eine Multipart-Anfrage und sendet sie an den Server. JavaScript ist in diesem Schritt nicht erforderlich.
Schritt 2: PHP-Verarbeitung des Datei-Arrays
Wenn das Formular an den Server gesendet wird, befüllt PHP das superglobale Array $_FILES. Der Aufbau unterscheidet sich jedoch von Einzeldatei-Uploads: Die Schlüssel name / tmp_name / size / error werden zu Arrays, bei denen der Index der laufenden Nummer der Datei entspricht.
Wir durchlaufen die Indizes und speichern jede Datei mit move_uploaded_file():
1 <?php 2 3 $target_dir = 'uploads/'; 4 5 if (isset($_FILES['fileUpload']['name'])) { 6 7 $total_files = count($_FILES['fileUpload']['name']); 8 9 for ($key = 0; $key < $total_files; $key++) { 10 11 // Skip empty fields (no file selected) 12 if (isset($_FILES['fileUpload']['name'][$key]) 13 && $_FILES['fileUpload']['size'][$key] > 0) { 14 15 $original_filename = $_FILES['fileUpload']['name'][$key]; 16 $target = $target_dir . basename($original_filename); 17 $tmp = $_FILES['fileUpload']['tmp_name'][$key]; 18 19 move_uploaded_file($tmp, $target); 20 } 21 } 22 }
Fügen Sie diesen Code in die functions.php Ihres Child-Themes oder in das Code Snippets-Plugin ein, und das Formular aus Schritt 1 wird Dateien entgegennehmen.
Beachten Sie, dass basename() den Pfad aus dem Dateinamen entfernt. Das schützt vor Path-Traversal-Angriffen: Ein Angreifer könnte ../../wp-config.php als Namen senden. basename() lässt nur wp-config.php übrig, aber die nächste Verteidigungsebene sollte dennoch die Prüfung der Dateiendung sein.
Schritt 3: Prüfung von Dateityp und Größe
Sie können nicht alles, was der Browser sendet, ungeprüft übernehmen. Mindestschutz: eine Whitelist der erlaubten Endungen und eine Größenbeschränkung.
Die Endungsprüfung erfolgt mit pathinfo() und dem Flag PATHINFO_EXTENSION. Konvertieren Sie in Kleinbuchstaben und vergleichen Sie mit den erlaubten Endungen:
1 $allowed_ext = array('jpg', 'jpeg', 'png', 'gif', 'bmp', 'pdf', 'doc', 'docx'); 2 $ext = strtolower(pathinfo($_FILES['fileUpload']['name'][$key], PATHINFO_EXTENSION)); 3 4 if (!in_array($ext, $allowed_ext)) { 5 $errors[$key] = 'Invalid file type: ' . $ext; 6 continue; 7 }
Die Größenprüfung ist noch einfacher; der Wert size in Bytes steht bereits in $_FILES:
1 $max_file_size = 5 * 1024 * 1024; // 5 MB 2 3 if ($_FILES['fileUpload']['size'][$key] > $max_file_size) { 4 $errors[$key] = 'File exceeds the allowed size (5 MB)'; 5 continue; 6 }
Platzieren Sie beide Prüfungen vor move_uploaded_file() in derselben Schleife. Dateien, die die Validierung nicht bestehen, gelangen nicht auf die Festplatte. Sammeln Sie Fehler in einem Array und zeigen Sie dem Benutzer nach der Verarbeitung eine Liste, welche Datei warum abgelehnt wurde. Ohne diese Rückmeldung sieht der Benutzer nur ein leeres Ergebnis und geht, in der Annahme, das Formular sei defekt. Geben Sie für jede abgelehnte Datei Name und Grund an, etwa „photo.png: ungültiger Typ" oder „archive.zip: Größe überschritten".
Schritt 4: Dateien beim Upload umbenennen
Wenn zwei Benutzer photo.jpg hochladen, überschreibt die zweite Datei die erste. Die Lösung besteht darin, beim Speichern einen eindeutigen Namen zu generieren. Der einfachste Ansatz: Leerzeichen durch Unterstriche ersetzen und time() anhängen.
1 if (isset($_FILES['fileUpload']['name'][$key]) 2 && $_FILES['fileUpload']['size'][$key] > 0) { 3 4 $original_filename = $_FILES['fileUpload']['name'][$key]; 5 6 // Extension separately 7 $ext = pathinfo($original_filename, PATHINFO_EXTENSION); 8 9 // Name without extension 10 $filename_without_ext = basename($original_filename, '.' . $ext); 11 12 // New name: no spaces + timestamp 13 $new_filename = str_replace(' ', '_', $filename_without_ext) 14 . '_' . time() . '.' . $ext; 15 16 move_uploaded_file( 17 $_FILES['fileUpload']['tmp_name'][$key], 18 $target_dir . $new_filename 19 ); 20 }
Der time()-Stempel sorgt für sekundengenaue Eindeutigkeit. Ersetzen Sie ihn bei stark frequentierten Projekten durch uniqid() oder eine UUID: Zwei gleichzeitige Anfragen innerhalb derselben Sekunde reichen aus, um eine Kollision zu verursachen und eine der Dateien zu verlieren. Zudem gibt time() das Upload-Datum im Dateinamen preis, was mitunter unerwünscht ist. Eine UUID aus ramsey/uuid beseitigt beide Probleme vollständig.
Schritt 5: Upload mit zusätzlichen Feldern pro Datei
Manchmal müssen Sie jeder Datei einen Titel, eine Beschreibung oder eine Kategorie zuordnen. Statt eines einzelnen Paares „Titel + Datei" erstellen Sie mehrere und benennen die Felder als Arrays:
1 <form action="" method="post" enctype="multipart/form-data"> 2 3 <input type="text" name="title[]" placeholder="Title"> 4 <input type="file" name="fileUpload[]"> 5 6 <input type="text" name="title[]" placeholder="Title"> 7 <input type="file" name="fileUpload[]"> 8 9 <input type="text" name="title[]" placeholder="Title"> 10 <input type="file" name="fileUpload[]"> 11 12 <input type="submit" name="Submit" value="Upload"> 13 14 </form>

Serverseitig sind $_POST['title'] und $_FILES['fileUpload']['name'] parallele Arrays. Index 0 im einen entspricht Index 0 im anderen. Die Verarbeitung nutzt dieselbe Schleife, nun jedoch mit Titelbindung:
1 $total_files = count($_FILES['fileUpload']['name']); 2 3 for ($key = 0; $key < $total_files; $key++) { 4 5 if ($_FILES['fileUpload']['size'][$key] === 0) { 6 continue; // empty field — skip 7 } 8 9 $title = isset($_POST['title'][$key]) ? $_POST['title'][$key] : ''; 10 $tmp = $_FILES['fileUpload']['tmp_name'][$key]; 11 $fname = basename($_FILES['fileUpload']['name'][$key]); 12 $target = $target_dir . time() . '_' . $fname; 13 14 if (move_uploaded_file($tmp, $target)) { 15 // Save $title and $target to the database 16 // or into an array for further processing 17 } 18 }
Stellen Sie sicher, dass die Anzahl der title[]-Felder mit der Anzahl der fileUpload[]-Felder im Formular übereinstimmt. Wenn der Benutzer Felder dynamisch per JavaScript hinzufügt, müssen alle Indizes lückenlos aufeinanderfolgen. PHP komprimiert lückenhafte Arrays nicht automatisch, sodass $_POST['title'][5] existieren kann, während $_FILES['fileUpload']['name'][5] nicht existiert.
In der Praxis deckt dieser Ansatz gut die Hälfte aller Kundenanforderungen ab: Bildergalerien mit Bildunterschriften, Dokumenten-Uploads mit Tags, Bewerbungseinreichungen mit Portfolios. Dasselbe Prinzip paralleler Arrays funktioniert für Beschreibung, Kategorie, Reihenfolge und beliebige weitere Felder.
⁉️🤔 Häufig gestellte Fragen
Warum erscheint die Datei nicht in $_FILES, obwohl das Formular abgesendet wurde?
In neun von zehn Fällen fehlt
enctype="multipart/form-data"im<form>-Tag. Ohne dieses Attribut sendet der Browser die Daten als Klartext, und PHP befüllt$_FILESnicht. Prüfen Sie außerdempost_max_sizeundupload_max_filesizein derphp.ini; das Überschreiten eines der Werte führt zu einem leeren Array ohne sichtbare Fehler.
Wie begrenze ich die gesamte Upload-Größe für eine einzelne Anfrage?
Die Direktive
post_max_sizein derphp.inibeschneidet den gesamten POST-Anfragekörper, einschließlich Dateien und Textfeldern. Der Standardwert laut PHP-Dokumentation beträgt 8 MB. Setzen Sie ihn höher alsupload_max_filesize, mit einem gewissen Overhead für die Multipart-Kodierung (etwa 20%). Erhöhen Sie für Massen-Uploads beide Parameter:upload_max_filesize = 20Mundpost_max_size = 25M.
Können Dateien asynchron per JavaScript hochgeladen werden?
Ja. Verwenden Sie
FormDataundfetchmitmethod: 'POST'. Fügen SiefileUpload[]-Felder mitformData.append('fileUpload[]', file)für jede ausgewählte Datei hinzu. Setzen Sie denContent-Type-Header nicht manuell; der Browser setzt automatischmultipart/form-datamit der korrekten Boundary. Nutzen Sie für visuelles Feedback einen Fortschrittsbalken viaXMLHttpRequest.upload.onprogress, dafetchden Fortschritt nicht direkt bereitstellt.
Was ist mit der Sicherheit: Ein Angreifer lädt ein PHP-Skript statt eines Bildes hoch?
Die Endungsprüfung aus Schritt 3 ist die erste Verteidigungslinie. Zweitens: Speichern Sie hochgeladene Dateien außerhalb des Web-Root-Ordners der Website oder in einem Verzeichnis mit einer
.htaccess, die die Ausführung unterbindet (php_flag engine off). Drittens: Benennen Sie Dateien beim Speichern um (Schritt 4); selbst wennshell.phpden Endungsfilter passiert, wird darausshell_1718300000.php, was ohne Ausführungsrechte ungefährlich ist. Viertens: Prüfen Sie den MIME-Typ nicht anhand von$_FILES['type'](wird vom Browser gesendet und ist leicht fälschbar), sondern viafinfo(PHPs eingebautes Fileinfo), das die Dateisignatur ausliest.
Was dieser Ansatz leistet: das vollständige Bild
Sie haben ein Upload-System gebaut, das drei Szenarien ab Werk beherrscht: Stapeldateien mit einem Button, Einzel-Upload mit Validierung und die Paarung „Datei + Metadaten" über parallele Arrays. Der Code umfasst rund dreißig Zeilen pro Schritt, nichts Überflüssiges.
Wenn Sie mit WordPress arbeiten, platzieren Sie den Handler in der functions.php Ihres Child-Themes oder im Code Snippets-Plugin. Bei reinem PHP legen Sie das Verarbeitungsskript einfach als Action-Ziel des Formulars fest.
Der Code aus diesem Artikel deckt die meisten typischen Upload-Aufgaben ab. Die verbleibenden Szenarien (Chunked Upload für große Dateien, direkter Upload in S3-kompatiblen Speicher sowie Drag-and-Drop mit Vorschau) werden in separaten Artikeln behandelt. Schreiben Sie in die Kommentare, welches für Ihr Projekt relevant ist, und wir priorisieren es entsprechend.



