
🚀 Caricamento di più file con HTML e PHP
Un utente deve caricare cinque immagini, ma il modulo ne accetta solo una. Oppure un cliente chiede il caricamento batch di documenti con un titolo associato a ciascun file. Non è fantasia: è un'attività standard che PHP risolve in trenta righe.
Il problema è che la maggior parte dei tutorial mostra il caricamento di file singoli. Il multi-upload funziona in modo diverso: un array di file invece di uno solo, mappatura su campi aggiuntivi, protezione contro la sovrascrittura. Di seguito trovi un'implementazione funzionante, da zero fino a uno snippet pronto all'uso.
💡 Panoramica rapida:
- Crea un modulo HTML con
enctype="multipart/form-data"e un campofilecon l'attributomultiple. - Elabora l'array
$_FILESin PHP: itera sugli indici e usamove_uploaded_file()per ciascun file. - Aggiungi la validazione di estensione e dimensione prima di salvare su disco.
- Per associare "file + titolo", usa array paralleli
title[]efileUpload[].
Una breve video-guida sullo stesso argomento, dal modulo HTML al salvataggio dei file in un database:
Step 1: modulo HTML per il caricamento di più file
Per prima cosa, costruisci il modulo. Punti chiave: l'attributo enctype impostato su multipart/form-data e un campo input con multiple. Senza enctype, il server semplicemente non riceverà i file; non è un errore di PHP ma una specifica HTTP.
Assegna al campo un nome in forma di array: fileUpload[]. Le parentesi quadre alla fine indicano a PHP che il server sta ricevendo una lista, non un file singolo.
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>
Il modulo qui sopra permette di selezionare un numero qualsiasi di file in una sola volta nella finestra di dialogo. Il browser li impacchetterà in una richiesta multipart e li invierà al server. In questa fase non serve JavaScript.
Step 2: elaborazione PHP dell'array di file
Quando il modulo viene inviato al server, PHP popola il superglobal $_FILES. Ma la struttura è diversa rispetto al caricamento di un file singolo: le chiavi name / tmp_name / size / error diventano array in cui l'indice corrisponde al numero progressivo del file.
Iteriamo sugli indici e salviamo ciascun file usando 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 }
Aggiungi questo codice al file functions.php del tuo tema child o al plugin Code Snippets, e il modulo dello step 1 inizierà ad accettare file.
Nota che basename() rimuove il percorso dal nome del file. Questo protegge dagli attacchi di path traversal: un malintenzionato potrebbe inviare ../../wp-config.php come nome. basename() lascia solo wp-config.php, ma il livello di difesa successivo deve comunque essere la validazione dell'estensione.
Step 3: controllo del tipo e della dimensione del file
Non puoi accettare "così come sono" tutti i file che il browser invia. Protezione minima: una whitelist di estensioni e un limite di dimensione.
Controllo dell'estensione tramite pathinfo() con il flag PATHINFO_EXTENSION. Converti in minuscolo e confronta con le estensioni consentite:
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 }
Il controllo della dimensione è ancora più semplice; il valore size in byte è già presente in $_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 }
Inserisci entrambi i controlli prima di move_uploaded_file() nello stesso ciclo. I file che non superano la validazione non arriveranno su disco. Raccogli gli errori in un array e, dopo l'elaborazione, mostra all'utente una lista con l'indicazione di quale file è stato rifiutato e perché. Senza questo passaggio, l'utente vedrà solo un risultato vuoto e se ne andrà, pensando che il modulo sia rotto. Per ogni file rifiutato, riporta il nome e il motivo, ad esempio "photo.png: tipo non valido" o "archive.zip: dimensione superata".
Step 4: rinominare i file durante il caricamento
Se due utenti caricano photo.jpg, il secondo file sovrascriverà il primo. La soluzione è generare un nome univoco al momento del salvataggio. L'approccio più semplice: sostituire gli spazi con underscore e aggiungere 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 }
Il timestamp time() garantisce unicità al secondo. Su progetti ad alto traffico, sostituiscilo con uniqid() o UUID: due richieste simultanee nello stesso secondo sono sufficienti per causare una collisione e perdere uno dei file. Inoltre, time() rivela la data di caricamento nel nome del file, cosa a volte indesiderata. Un UUID da ramsey/uuid elimina completamente entrambi i problemi.
Step 5: caricamento con campi aggiuntivi per ciascun file
A volte serve associare a ogni file un titolo, una descrizione o una categoria. Invece di una singola coppia "titolo + file", ne crei diverse, nominando i campi come array:
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>

Lato server, $_POST['title'] e $_FILES['fileUpload']['name'] sono array paralleli. L'indice 0 nell'uno corrisponde all'indice 0 nell'altro. L'elaborazione usa lo stesso ciclo, ma ora con l'associazione del titolo:
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 }
Assicurati che il numero di campi title[] corrisponda al numero di campi fileUpload[] nel modulo. Se l'utente aggiunge campi dinamicamente via JavaScript, tutti gli indici devono essere consecutivi e senza buchi. PHP non compatta automaticamente gli array sparsi, quindi $_POST['title'][5] potrebbe esistere mentre $_FILES['fileUpload']['name'][5] no.
Nella pratica, questo approccio copre una buona metà delle richieste dei clienti: gallerie di immagini con didascalie, caricamento documenti con tag, invio di curriculum con portfolio. Lo stesso principio degli array paralleli funziona per descrizione, categoria, ordinamento e qualsiasi altro campo.
⁉️🤔 Domande frequenti
Perché il file non compare in $_FILES anche se il modulo è stato inviato?
Nove volte su dieci, manca
enctype="multipart/form-data"nel tag<form>. Senza, il browser invia i dati come testo semplice e PHP non popola$_FILES. Verifica anchepost_max_sizeeupload_max_filesizenelphp.ini; il superamento di uno dei due produce un array vuoto senza errori visibili.
Come posso limitare la dimensione totale del caricamento per una singola richiesta?
La direttiva
post_max_sizenelphp.initronca l'intero corpo della richiesta POST, inclusi file e campi di testo. Il valore predefinito secondo la documentazione PHP è 8 MB. Impostalo più alto diupload_max_filesizecon un margine per la codifica multipart (circa il 20%). Per caricamenti massivi, aumenta entrambi i parametri:upload_max_filesize = 20Mepost_max_size = 25M.
È possibile caricare file in modo asincrono via JavaScript?
Sì. Usa
FormDataefetchconmethod: 'POST'. Aggiungi i campifileUpload[]usandoformData.append('fileUpload[]', file)per ogni file selezionato. Non impostare manualmente l'headerContent-Type; il browser imposterà automaticamentemultipart/form-datacon il boundary corretto. Per un feedback visivo, usa una barra di progresso tramiteXMLHttpRequest.upload.onprogress, poichéfetchnon espone direttamente l'avanzamento.
E la sicurezza: un malintenzionato carica uno script PHP invece di un'immagine?
Il controllo dell'estensione dello step 3 è la prima linea di difesa. Seconda: archivia i file caricati FUORI dalla root del sito o in una directory con un
.htaccessche disabilita l'esecuzione (php_flag engine off). Terza: rinomina i file al momento del salvataggio (step 4); anche seshell.phpsupera il filtro dell'estensione, diventashell_1718300000.php, che non è pericoloso senza diritti di esecuzione. Quarta: verifica il MIME type non da$_FILES['type'](inviato dal browser, facilmente falsificabile), ma tramitefinfo(Fileinfo integrato in PHP), che legge la firma del file.
Cosa offre questo approccio: il quadro completo
Hai costruito un sistema di upload che gestisce tre scenari su due piedi: file multipli con un solo pulsante, caricamento singolo con validazione e abbinamento "file + metadati" tramite array paralleli. Il codice è di circa trenta righe per ogni step, niente di superfluo.
Se lavori in WordPress, inserisci il gestore nel file functions.php del tuo tema child o nel plugin Code Snippets. In PHP puro, metti semplicemente lo script di elaborazione come destinazione dell'action del modulo.
Il codice di questo articolo copre la maggior parte delle tipiche esigenze di upload. Gli scenari restanti (caricamento a blocchi per file grandi, upload diretto su storage compatibile S3 e drag-and-drop con anteprima) saranno trattati in articoli separati. Scrivi nei commenti quale è rilevante per il tuo progetto e gli daremo priorità.



