Skip to content

Alles für WordPress, Webentwicklung — und mehr

🚀 Mehrere dateien mit HTML und PHP hochladen

🚀 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 einem file-Feld mit dem Attribut multiple.
  • Verarbeiten Sie das $_FILES-Array in PHP: Durchlaufen Sie die Indizes und nutzen Sie move_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[] und fileUpload[].

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
5if (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
4if (!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
3if ($_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.

1if (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>
Upload-Formular mit zusätzlichen Titelfeldern

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
3for ($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 $_FILES nicht. Prüfen Sie außerdem post_max_size und upload_max_filesize in der php.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_size in der php.ini beschneidet den gesamten POST-Anfragekörper, einschließlich Dateien und Textfeldern. Der Standardwert laut PHP-Dokumentation beträgt 8 MB. Setzen Sie ihn höher als upload_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 = 20M und post_max_size = 25M.

Können Dateien asynchron per JavaScript hochgeladen werden?

Ja. Verwenden Sie FormData und fetch mit method: 'POST'. Fügen Sie fileUpload[]-Felder mit formData.append('fileUpload[]', file) für jede ausgewählte Datei hinzu. Setzen Sie den Content-Type-Header nicht manuell; der Browser setzt automatisch multipart/form-data mit der korrekten Boundary. Nutzen Sie für visuelles Feedback einen Fortschrittsbalken via XMLHttpRequest.upload.onprogress, da fetch den 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 wenn shell.php den Endungsfilter passiert, wird daraus shell_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 via finfo (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.