Skip to content

Allt om WordPress, webbutveckling — och mer därtill

🚀 Ladda upp flera filer med HTML och PHP

🚀 Ladda upp flera filer med HTML och PHP

En användare behöver ladda upp fem bilder, men formuläret tar bara emot en. Eller en kund ber om batchuppladdning av dokument med en titel kopplad till varje fil. Det här är ingen fantasi: det är en standarduppgift som PHP löser på trettio rader.

Problemet är att de flesta guider visar uppladdning av enstaka filer. Multiuppladdning fungerar annorlunda: en array med filer istället för en, mappning mot ytterligare fält, skydd mot överskrivning. Nedan följer en fungerande implementation från grunden till ett färdigt kodsnutt.

💡 Snabb översikt:

  • Skapa ett HTML-formulär med enctype="multipart/form-data" och ett file-fält med attributet multiple.
  • Bearbeta $_FILES-arrayen i PHP: loopa genom index och använd move_uploaded_file() för varje fil.
  • Lägg till validering av filändelse och storlek innan du sparar till disk.
  • För att länka "fil + titel", använd parallella arrayer title[] och fileUpload[].

En snabb videogenomgång av samma ämne, från HTML-formulär till att spara filer i en databas:

Steg 1: HTML-formulär för uppladdning av flera filer

Börja med att bygga formuläret. Viktiga punkter: attributet enctype satt till multipart/form-data och ett inmatningsfält med multiple. Utan enctype kommer servern helt enkelt inte att ta emot filer; detta är inget PHP-fel utan en del av HTTP-specifikationen.

Namnge fältet som en array: fileUpload[]. Hakparenteserna på slutet talar om för PHP att servern tar emot en lista, inte en enskild fil.

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>

Formuläret ovan tillåter att man väljer valfritt antal filer samtidigt i fildialogen. Webbläsaren packar dem i en multipart-förfrågan och skickar dem till servern. Ingen JavaScript behövs i detta skede.

Steg 2: PHP-bearbetning av fil-arrayen

När formuläret skickas till servern fyller PHP den superglobala arrayen $_FILES. Men strukturen skiljer sig från uppladdning av enstaka filer: nycklarna name / tmp_name / size / error blir arrayer där indexet motsvarar filens sekvensnummer.

Vi loopar genom indexen och sparar varje fil med 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}

Lägg till denna kod i ditt barntemas functions.php eller i tillägget Code Snippets, så börjar formuläret från steg 1 att ta emot filer.

Observera att basename() tar bort sökvägen från filnamnet. Detta skyddar mot path traversal-attacker: en angripare skulle kunna skicka ../../wp-config.php som namn. basename() lämnar bara kvar wp-config.php, men nästa försvarslager bör ändå vara validering av filändelse.

Steg 3: Kontroll av filtyp och storlek

Du kan inte acceptera allt webbläsaren skickar "som det är". Minimiskydd: en vitlista över tillåtna filändelser och en storleksgräns.

Filändelsekontroll med pathinfo() och flaggan PATHINFO_EXTENSION. Konvertera till gemener och jämför mot tillåtna ändelser:

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}

Storlekskontroll är ännu enklare; värdet size i byte finns redan i $_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}

Placera båda kontrollerna före move_uploaded_file() i samma loop. Filer som inte klarar valideringen når inte disken. Samla fel i en array och visa efter bearbetningen en lista för användaren som visar vilken fil som misslyckades och varför. Utan detta ser användarna bara ett tomt resultat och lämnar sidan i tron att formuläret är trasigt. För varje avvisad fil, rapportera namnet och orsaken, som "photo.png: ogiltig filtyp" eller "archive.zip: storleksgränsen överskriden".

Steg 4: Byta namn på filer vid uppladdning

Om två användare laddar upp photo.jpg kommer den andra filen att skriva över den första. Lösningen är att generera ett unikt namn vid sparandet. Den enklaste metoden: ersätt mellanslag med understreck och lägg till time().

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}

time()-stämpeln ger unikhet ner på sekundnivå. I projekt med hög trafik, ersätt den med uniqid() eller UUID: två samtidiga förfrågningar inom samma sekund räcker för att orsaka en kollision och förlora en av filerna. Dessutom avslöjar time() uppladdningsdatumet i filnamnet, vilket ibland är oönskat. En UUID från ramsey/uuid eliminerar båda problemen helt.

Steg 5: Uppladdning med extra fält för varje fil

Ibland behöver du koppla en titel, beskrivning eller kategori till varje fil. Istället för ett "titel + fil"-par skapar du flera och namnger fälten som arrayer:

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>
Uppladdningsformulär med extra titelfält

På serversidan är $_POST['title'] och $_FILES['fileUpload']['name'] parallella arrayer. Index 0 i den ena motsvarar index 0 i den andra. Bearbetningen använder samma loop, men nu med titelbindning:

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}

Se till att antalet title[]-fält matchar antalet fileUpload[]-fält i formuläret. Om användaren lägger till fält dynamiskt via JavaScript måste alla index vara konsekutiva utan luckor. PHP kollapsar inte glesa arrayer automatiskt, så $_POST['title'][5] kan finnas medan $_FILES['fileUpload']['name'][5] inte gör det.

I praktiken täcker detta tillvägagångssätt en god hälft av kunduppgifterna: bildgallerier med bildtexter, dokumentuppladdningar med taggar, cv-inlämningar med portföljer. Samma princip med parallella arrayer fungerar för beskrivning, kategori, ordning och alla andra fält.

⁉️🤔 Vanliga frågor

Varför syns inte filen i $_FILES trots att formuläret skickades?

I nio fall av tio saknas enctype="multipart/form-data" i <form>-taggen. Utan den skickar webbläsaren data som vanlig text, och PHP fyller inte i $_FILES. Kontrollera också post_max_size och upload_max_filesize i php.ini; om någon av dessa överskrids blir arrayen tom utan synliga fel.

Hur begränsar jag den totala uppladdningsstorleken för en enskild förfrågan?

Direktivet post_max_size i php.ini kapar hela POST-förfrågans body, inklusive filer och textfält. Standardvärdet enligt PHP-dokumentationen är 8 MB. Sätt det högre än upload_max_filesize med lite overhead för multipart-kodning (ungefär 20%). För massuppladdningar, öka båda parametrarna: upload_max_filesize = 20M och post_max_size = 25M.

Kan filer laddas upp asynkront via JavaScript?

Ja. Använd FormData och fetch med method: 'POST'. Lägg till fileUpload[]-fält med formData.append('fileUpload[]', file) för varje vald fil. Sätt inte Content-Type-headern manuellt; webbläsaren sätter automatiskt multipart/form-data med korrekt boundary. För visuell återkoppling, använd en förloppsindikator via XMLHttpRequest.upload.onprogress eftersom fetch inte exponerar förloppet direkt.

Hur är det med säkerheten: en angripare laddar upp ett PHP-skript istället för en bild?

Filändelsekontrollen från steg 3 är den första försvarslinjen. Andra: lagra uppladdade filer UTANFÖR webbplatsens rotmapp eller i en katalog med en .htaccess som inaktiverar exekvering (php_flag engine off). Tredje: byt namn på filer vid sparandet (steg 4); även om shell.php passerar ändelsefiltret blir den shell_1718300000.php, vilket inte är farligt utan exekveringsrättigheter. Fjärde: kontrollera MIME-typen inte från $_FILES['type'] (skickas av webbläsaren, lätt att förfalska), utan via finfo (PHP:s inbyggda Fileinfo), som läser filens signatur.

Vad detta tillvägagångssätt ger: helhetsbilden

Du har byggt ett uppladdningssystem som hanterar tre scenarier direkt: batchfiler med en knapp, enstaka uppladdning med validering och "fil + metadata"-parning via parallella arrayer. Koden är ungefär trettio rader per steg, inget extra.

Om du arbetar i WordPress, placera hanteraren i ditt barntemas functions.php eller i tillägget Code Snippets. För ren PHP, lägg helt enkelt bearbetningsskriptet som formulärets action-mål.

Koden från denna artikel täcker de flesta typiska uppladdningsbehov. Återstående scenarier (delad uppladdning för stora filer, direktuppladdning till S3-kompatibel lagring och dra-och-släpp med förhandsvisning) kommer att behandlas i separata artiklar. Skriv i kommentarerna vilken som är relevant för ditt projekt, så prioriterar vi den.