
🚀 Laste opp flere filer med HTML og PHP
En bruker må laste opp fem bilder, men skjemaet godtar bare ett. Eller en kunde ber om masseopplasting av dokumenter med en tittel knyttet til hver fil. Dette er ikke fantasi: det er en standardoppgave som PHP løser på tretti linjer.
Problemet er at de fleste veiledninger viser opplasting av én enkelt fil. Multiopplasting fungerer annerledes: en matrise med filer i stedet for én, kobling til tilleggsfelt, beskyttelse mot overskriving. Nedenfor finner du en fungerende implementasjon fra bunnen av til en klar-til-bruk-snutt.
💡 Rask oversikt:
- Lag et HTML-skjema med
enctype="multipart/form-data"og etfile-felt medmultiple-attributtet. - Behandle
$_FILES-matrisen i PHP: gå gjennom indekser og brukmove_uploaded_file()for hver fil. - Legg til validering av filtype og størrelse før lagring til disk.
- For å koble «fil + tittel», bruk parallelle matriser
title[]ogfileUpload[].
En rask videogjennomgang av samme tema, fra HTML-skjema til lagring av filer i en database:
Trinn 1: HTML-skjema for opplasting av flere filer
Først bygger du skjemaet. Nøkkelpunkter: enctype-attributtet satt til multipart/form-data og et inndatafelt med multiple. Uten enctype vil serveren rett og slett ikke motta filer; dette er ikke en PHP-feil, men HTTP-spesifikasjonen.
Navngi feltet som en matrise: fileUpload[]. Hakeparentesene på slutten forteller PHP at serveren mottar en liste, ikke en enkelt 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>
Skjemaet ovenfor tillater å velge et vilkårlig antall filer samtidig i fildialogen. Nettleseren vil pakke dem inn i en multipart-forespørsel og sende dem til serveren. Ingen JavaScript er nødvendig på dette stadiet.
Trinn 2: PHP-behandling av filmatrisen
Når skjemaet sendes til serveren, fyller PHP den superglobale $_FILES-matrisen. Men strukturen skiller seg fra opplasting av én enkelt fil: nøklene name / tmp_name / size / error blir matriser der indeksen tilsvarer filens sekvensnummer.
Vi går gjennom indeksene og lagrer hver fil ved hjelp av 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 }
Legg denne koden til i barnetemaets functions.php eller i Code Snippets-utvidelsen, så vil skjemaet fra trinn 1 begynne å motta filer.
Merk at basename() fjerner stien fra filnavnet. Dette beskytter mot path traversal-angrep: en angriper kan sende ../../wp-config.php som navn. basename() etterlater bare wp-config.php, men det neste forsvarslaget bør fortsatt være validering av filtype.
Trinn 3: Sjekke filtype og størrelse
Du kan ikke akseptere alt nettleseren sender «som det er». Minimumsbeskyttelse: en hviteliste over filtyper og en størrelsesgrense.
Filtypesjekk ved hjelp av pathinfo() med PATHINFO_EXTENSION-flagget. Konverter til små bokstaver og sammenlign med tillatte filtyper:
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 }
Størrelsessjekk er enda enklere; size-verdien i byte ligger allerede i $_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 }
Plasser begge sjekkene før move_uploaded_file() i samme løkke. Filer som ikke består valideringen, vil ikke nå disken. Samle feil i en matrise og vis etter behandlingen en liste til brukeren som viser hvilken fil som feilet og hvorfor. Uten dette vil brukerne bare se et tomt resultat og gå sin vei, i den tro at skjemaet er ødelagt. For hver avviste fil, rapporter navn og årsak, som «photo.png: ugyldig filtype» eller «archive.zip: størrelsen overskredet».
Trinn 4: Gi filer nytt navn under opplasting
Hvis to brukere laster opp photo.jpg, vil den andre filen overskrive den første. Løsningen er å generere et unikt navn ved lagring. Den enkleste tilnærmingen: erstatt mellomrom med understreker og legg til time().
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 }
time()-stempelet gir unikhet ned til sekundet. I prosjekter med høy trafikk bør du erstatte det med uniqid() eller UUID: to samtidige forespørsler innen samme sekund er nok til å forårsake en kollisjon og miste én av filene. time() avslører også opplastingsdatoen i filnavnet, noe som noen ganger er uønsket. En UUID fra ramsey/uuid eliminerer begge problemene fullstendig.
Trinn 5: Opplasting med tilleggsfelt for hver fil
Noen ganger må du knytte en tittel, beskrivelse eller kategori til hver fil. I stedet for ett «tittel + fil»-par oppretter du flere, og navngir feltene som matriser:
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>

På serversiden er $_POST['title'] og $_FILES['fileUpload']['name'] parallelle matriser. Indeks 0 i den ene tilsvarer indeks 0 i den andre. Behandlingen bruker samme løkke, men nå med tittelbinding:
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 }
Sørg for at antallet title[]-felt samsvarer med antallet fileUpload[]-felt i skjemaet. Hvis brukeren legger til felt dynamisk via JavaScript, må alle indekser være fortløpende uten hull. PHP komprimerer ikke automatisk spredte matriser, så $_POST['title'][5] kan eksistere mens $_FILES['fileUpload']['name'][5] ikke gjør det.
I praksis dekker denne tilnærmingen en god halvpart av kundeoppgavene: bildegallerier med bildetekster, dokumentopplastinger med tagger, CV-innsendinger med porteføljer. Det samme prinsippet med parallelle matriser fungerer for beskrivelse, kategori, rekkefølge og alle andre felt.
⁉️🤔 Ofte stilte spørsmål
Hvorfor dukker ikke filen opp i $_FILES selv om skjemaet ble sendt inn?
Ni av ti ganger mangler
enctype="multipart/form-data"i<form>-taggen. Uten den sender nettleseren data som ren tekst, og PHP fyller ikke$_FILES. Sjekk ogsåpost_max_sizeogupload_max_filesizeiphp.ini; overskridelse av noen av dem resulterer i en tom matrise uten synlige feil.
Hvordan begrenser jeg den totale opplastingsstørrelsen for én enkelt forespørsel?
Direktivet
post_max_sizeiphp.iniavkorter hele POST-forespørselens kropp, inkludert filer og tekstfelt. Standardverdien ifølge PHP-dokumentasjonen er 8 MB. Sett den høyere ennupload_max_filesizemed noe overhead for multipart-koding (omtrent 20%). For masseopplastinger, øk begge parameterne:upload_max_filesize = 20Mogpost_max_size = 25M.
Kan filer lastes opp asynkront via JavaScript?
Ja. Bruk
FormDataogfetchmedmethod: 'POST'. Legg tilfileUpload[]-felt ved hjelp avformData.append('fileUpload[]', file)for hver valgte fil. Ikke settContent-Type-headeren manuelt; nettleseren vil automatisk settemultipart/form-datamed riktig grense. For visuell tilbakemelding, bruk en fremdriftslinje viaXMLHttpRequest.upload.onprogress, sidenfetchikke eksponerer fremdrift direkte.
Hva med sikkerhet: en angriper laster opp et PHP-skript i stedet for et bilde?
Filtypesjekk fra trinn 3 er første forsvarslinje. Andre: lagre opplastede filer UTENFOR nettstedets rotmappe eller i en katalog med en
.htaccesssom deaktiverer kjøring (php_flag engine off). Tredje: gi filer nytt navn ved lagring (trinn 4); selv omshell.phppasserer filteret for filtype, blir den tilshell_1718300000.php, som ikke er farlig uten kjøringsrettigheter. Fjerde: sjekk MIME-typen ikke fra$_FILES['type'](sendt av nettleseren, lett å forfalske), men viafinfo(PHPs innebygde Fileinfo), som leser filens signatur.
Hva denne tilnærmingen gir: det komplette bildet
Du har bygget et opplastingssystem som håndterer tre scenarier rett ut av esken: batchfiler med én knapp, enkeltopplasting med validering, og «fil + metadata»-kobling gjennom parallelle matriser. Koden er rundt tretti linjer for hvert trinn, ikke noe ekstra.
Hvis du jobber i WordPress, plasser behandleren i barnetemaets functions.php eller i Code Snippets-utvidelsen. På vanlig PHP, legg ganske enkelt behandlingsskriptet i skjemaets action-mål.
Koden fra denne artikkelen dekker de fleste typiske opplastingsoppgaver. De resterende scenarioene (delt opplasting for store filer, direkte opplasting til S3-kompatibel lagring, og dra-og-slipp med forhåndsvisning) vil bli dekket i separate artikler. Skriv i kommentarfeltet hvilken som er relevant for ditt prosjekt, så prioriterer vi den.



