Skip to content

Kaikki WordPressistä, web-kehityksestä — ja paljon muuta

🚀 Useiden tiedostojen lataaminen HTML:n ja PHP:n avulla

🚀 Useiden tiedostojen lataaminen HTML:n ja PHP:n avulla

Käyttäjän pitää ladata viisi kuvaa, mutta lomake hyväksyy vain yhden. Tai asiakas pyytää joukkodokumenttien latausta, jossa jokaiseen tiedostoon liitetään otsikko. Tämä ei ole fantasiaa: se on perustehtävä, jonka PHP ratkaisee kolmessakymmenessä rivissä.

Ongelma on, että useimmat oppaat näyttävät vain yhden tiedoston latauksen. Monilataus toimii eri tavalla: kyseessä on tiedostotaulukko yhden tiedoston sijaan, sidonta lisäkenttiin ja suojaus ylikirjoitusta vastaan. Alla on toimiva toteutus tyhjästä valmiiksi koodinpätkäksi.

💡 Pikaopas:

  • Luo HTML-lomake, jossa on enctype="multipart/form-data" ja file-kenttä, jossa on multiple-attribuutti.
  • Käsittele $_FILES-taulukko PHP:ssä: kierrä indeksit läpi ja käytä move_uploaded_file()-funktiota jokaiselle tiedostolle.
  • Lisää pääte- ja kokotarkistus ennen levylle tallennusta.
  • "Tiedosto + otsikko" -sidontaan käytä rinnakkaistaulukoita title[] ja fileUpload[].

Nopea videoesittely samasta aiheesta HTML-lomakkeesta tiedostojen tallentamiseen tietokantaan:

Vaihe 1: HTML-lomake usean tiedoston lataamiseen

Rakenna ensin lomake. Avainkohdat: enctype-attribuutin arvoksi multipart/form-data ja input-kenttä, jossa on multiple. Ilman enctype-määritystä palvelin ei yksinkertaisesti vastaanota tiedostoja; tämä ei ole PHP-virhe vaan HTTP-spesifikaation mukainen asia.

Nimeä kenttä taulukkona: fileUpload[]. Hakasulkeet lopussa kertovat PHP:lle, että palvelin vastaanottaa listan, ei yksittäistä tiedostoa.

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>

Yllä oleva lomake mahdollistaa minkä tahansa määrän tiedostoja valitsemisen kerralla tiedostovalintaikkunassa. Selain pakkaa ne multipart-pyyntöön ja lähettää palvelimelle. JavaScriptiä ei tarvita tässä vaiheessa.

Vaihe 2: Tiedostotaulukon PHP-käsittely

Kun lomake lähetetään palvelimelle, PHP täyttää superglobaalin $_FILES-taulukon. Rakenne kuitenkin poikkeaa yhden tiedoston latauksesta: avaimet name / tmp_name / size / error muuttuvat taulukoiksi, joissa indeksi vastaa tiedoston järjestysnumeroa.

Kierrämme indeksit läpi ja tallennamme jokaisen tiedoston move_uploaded_file()-funktiolla:

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}

Lisää tämä koodi lapsiteemasi functions.php-tiedostoon tai Code Snippets -lisäosaan, niin vaiheen 1 lomake alkaa vastaanottaa tiedostoja.

Huomaa, että basename() poistaa polun tiedostonimestä. Tämä suojaa polun läpikäyntihyökkäyksiltä: hyökkääjä voisi lähettää nimenä ../../wp-config.php. basename() jättää jäljelle vain wp-config.php, mutta seuraavan puolustuskerroksen tulisi silti olla päätetarkistus.

Vaihe 3: Tiedostotyypin ja koon tarkistus

Et voi hyväksyä sellaisenaan kaikkea, mitä selain lähettää. Minimisuojaus: sallittujen päätteiden lista ja kokorajoitus.

Päätetarkistus pathinfo()-funktiolla ja PATHINFO_EXTENSION-lipulla. Muunna pieniksi kirjaimiksi ja vertaa sallittuihin päätteisiin:

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}

Kokotarkistus on vielä yksinkertaisempi; size-arvo tavuina on jo $_FILES-taulukossa:

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}

Sijoita molemmat tarkistukset ennen move_uploaded_file()-kutsua saman silmukan sisällä. Tiedostot, jotka eivät läpäise validointia, eivät päädy levylle. Kerää virheet taulukkoon ja näytä käsittelyn jälkeen käyttäjälle lista, josta ilmenee mikä tiedosto hylättiin ja miksi. Ilman tätä käyttäjät näkevät vain tyhjän tuloksen ja poistuvat olettaen lomakkeen olevan rikki. Ilmoita jokaisesta hylätystä tiedostosta nimi ja syy, kuten "photo.png: virheellinen tyyppi" tai "archive.zip: koko ylitetty".

Vaihe 4: Tiedostojen uudelleennimeäminen latauksen yhteydessä

Jos kaksi käyttäjää lataa tiedoston photo.jpg, toinen tiedosto ylikirjoittaa ensimmäisen. Ratkaisu on luoda yksilöllinen nimi tallennettaessa. Yksinkertaisin tapa: korvaa välilyönnit alaviivoilla ja lisää 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()-leima tarjoaa yksilöllisyyden sekunnin tarkkuudella. Paljon liikennettä sisältävissä projekteissa korvaa se uniqid()-funktiolla tai UUID:llä: kaksi samanaikaista pyyntöä saman sekunnin sisällä riittää aiheuttamaan törmäyksen ja kadottamaan toisen tiedostoista. Lisäksi time() paljastaa latauspäivämäärän tiedostonimessä, mikä on joskus epätoivottavaa. UUID ramsey/uuid-kirjastosta poistaa molemmat ongelmat kokonaan.

Vaihe 5: Lataus lisäkentillä jokaiselle tiedostolle

Joskus jokaiseen tiedostoon pitää liittää otsikko, kuvaus tai kategoria. Yhden "otsikko + tiedosto" -parin sijaan luot useita ja nimeät kentät taulukoina:

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>
Latauslomake lisäotsikkokentillä

Palvelimen puolella $_POST['title'] ja $_FILES['fileUpload']['name'] ovat rinnakkaistaulukoita. Indeksi 0 toisessa vastaa indeksiä 0 toisessa. Käsittely käyttää samaa silmukkaa, mutta nyt otsikon sidonnalla:

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}

Varmista, että title[]-kenttien määrä vastaa fileUpload[]-kenttien määrää lomakkeessa. Jos käyttäjä lisää kenttiä dynaamisesti JavaScriptillä, kaikkien indeksien on oltava peräkkäisiä ilman aukkoja. PHP ei automaattisesti tiivistä harvoja taulukoita, joten $_POST['title'][5] voi olla olemassa, vaikka $_FILES['fileUpload']['name'][5] ei olisi.

Käytännössä tämä lähestymistapa kattaa hyvän puolet asiakastehtävistä: kuvagalleriat kuvateksteillä, dokumenttilataukset tunnisteilla, ansioluettelolähetykset portfolion kera. Sama rinnakkaistaulukoiden periaate toimii kuvaukselle, kategorialle, järjestykselle ja kaikille muille kentille.

⁉️🤔 Usein kysytyt kysymykset

Miksi tiedosto ei näy $_FILES-taulukossa, vaikka lomake lähetettiin?

Yhdeksän kertaa kymmenestä enctype="multipart/form-data" puuttuu <form>-tagista. Ilman sitä selain lähettää datan pelkkänä tekstinä, eikä PHP täytä $_FILES-taulukkoa. Tarkista myös post_max_size ja upload_max_filesize php.ini-tiedostosta; kumman tahansa ylittäminen johtaa tyhjään taulukkoon ilman näkyviä virheitä.

Miten rajoitan yksittäisen pyynnön kokonaislatauskokoa?

post_max_size-direktiivi php.ini-tiedostossa katkaisee koko POST-pyynnön rungon, mukaan lukien tiedostot ja tekstikentät. Oletusarvo PHP-dokumentaation mukaan on 8 Mt. Aseta se suuremmaksi kuin upload_max_filesize ja lisää hieman ylimääräistä multipart-koodausta varten (noin 20%). Joukkolatauksia varten kasvata molempia parametreja: upload_max_filesize = 20M ja post_max_size = 25M.

Voiko tiedostoja ladata asynkronisesti JavaScriptillä?

Kyllä. Käytä FormData- ja fetch-rajapintoja method: 'POST'-asetuksella. Lisää fileUpload[]-kentät käyttäen formData.append('fileUpload[]', file) jokaiselle valitulle tiedostolle. Älä aseta Content-Type-otsikkoa manuaalisesti; selain asettaa automaattisesti multipart/form-data-tyypin oikealla rajausmerkillä. Visuaalista palautetta varten käytä edistymispalkkia XMLHttpRequest.upload.onprogress-tapahtuman kautta, sillä fetch ei tarjoa edistymistietoa suoraan.

Entä tietoturva: hyökkääjä lataa PHP-skriptin kuvan sijasta?

Vaiheen 3 päätetarkistus on ensimmäinen puolustuslinja. Toinen: tallenna ladatut tiedostot sivuston juurikansion ULKOPUOLELLE tai hakemistoon, jossa .htaccess estää suorituksen (php_flag engine off). Kolmas: nimeä tiedostot uudelleen tallennettaessa (vaihe 4); vaikka shell.php läpäisisi päätesuodattimen, siitä tulee shell_1718300000.php, mikä ei ole vaarallinen ilman suoritusoikeuksia. Neljäs: tarkista MIME-tyyppi ei $_FILES['type']-kentästä (selaimen lähettämä, helposti väärennettävissä), vaan finfo-funktiolla (PHP:n sisäänrakennettu Fileinfo), joka lukee tiedoston allekirjoituksen.

Mitä tämä lähestymistapa tarjoaa: kokonaiskuva

Olet rakentanut latausjärjestelmän, joka hoitaa kolme skenaariota suoraan laatikosta: joukkotiedostot yhdellä painikkeella, yksittäislatauksen validoinnilla ja "tiedosto + metadata" -parituksen rinnakkaistaulukoiden avulla. Koodia on noin kolmekymmentä riviä jokaista vaihetta kohden, ei mitään ylimääräistä.

Jos työskentelet WordPressissä, sijoita käsittelijä lapsiteemasi functions.php-tiedostoon tai Code Snippets -lisäosaan. Pelkällä PHP:llä laita käsittelyskripti yksinkertaisesti lomakkeen action-kohteen osoitteeseen.

Tämän artikkelin koodi kattaa useimmat tyypilliset lataustehtävät. Jäljelle jäävät skenaariot (paloiteltu lataus suurille tiedostoille, suora lataus S3-yhteensopivaan tallennustilaan ja raahaa ja pudota esikatselulla) käsitellään erillisissä artikkeleissa. Kirjoita kommentteihin, mikä niistä on relevantti projektisi kannalta, niin priorisoimme sen.