Skip to content

Alt om WordPress, webutvikling — og mer til

🚀 Laste opp flere filer med HTML og PHP

🚀 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 et file-felt med multiple-attributtet.
  • Behandle $_FILES-matrisen i PHP: gå gjennom indekser og bruk move_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[] og fileUpload[].

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
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}

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

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()-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>
Opplastingsskjema med ekstra tittelfelt

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
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}

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_size og upload_max_filesize i php.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_size i php.ini avkorter hele POST-forespørselens kropp, inkludert filer og tekstfelt. Standardverdien ifølge PHP-dokumentasjonen er 8 MB. Sett den høyere enn upload_max_filesize med noe overhead for multipart-koding (omtrent 20%). For masseopplastinger, øk begge parameterne: upload_max_filesize = 20M og post_max_size = 25M.

Kan filer lastes opp asynkront via JavaScript?

Ja. Bruk FormData og fetch med method: 'POST'. Legg til fileUpload[]-felt ved hjelp av formData.append('fileUpload[]', file) for hver valgte fil. Ikke sett Content-Type-headeren manuelt; nettleseren vil automatisk sette multipart/form-data med riktig grense. For visuell tilbakemelding, bruk en fremdriftslinje via XMLHttpRequest.upload.onprogress, siden fetch ikke 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 .htaccess som deaktiverer kjøring (php_flag engine off). Tredje: gi filer nytt navn ved lagring (trinn 4); selv om shell.php passerer filteret for filtype, blir den til shell_1718300000.php, som ikke er farlig uten kjøringsrettigheter. Fjerde: sjekk MIME-typen ikke fra $_FILES['type'] (sendt av nettleseren, lett å forfalske), men via finfo (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.