
🚀 Téléversement de plusieurs fichiers avec HTML et PHP
Un utilisateur doit téléverser cinq images, mais le formulaire n’en accepte qu’une seule. Ou un client demande un chargement groupé de documents avec un titre associé à chaque fichier. Ce n’est pas de la science-fiction: c’est une tâche standard que PHP résout en trente lignes.
Le problème, c’est que la plupart des tutoriels montrent le téléversement d’un fichier unique. Le multi-upload fonctionne différemment: un tableau de fichiers au lieu d’un seul, une correspondance avec des champs supplémentaires, une protection contre l’écrasement. Voici une implémentation fonctionnelle, de zéro jusqu’à un extrait prêt à l’emploi.
💡 Aperçu rapide:
- Créez un formulaire HTML avec
enctype="multipart/form-data"et un champfiledoté de l’attributmultiple. - Traitez le tableau
$_FILESen PHP: parcourez les indices et utilisezmove_uploaded_file()pour chaque fichier. - Ajoutez une validation de l’extension et de la taille avant l’enregistrement sur le disque.
- Pour lier «fichier + titre», utilisez les tableaux parallèles
title[]etfileUpload[].
Une rapide démonstration vidéo du même sujet, du formulaire HTML à l’enregistrement des fichiers en base de données:
Étape 1: formulaire HTML pour téléverser plusieurs fichiers
Commencez par construire le formulaire. Points clés: l’attribut enctype positionné à multipart/form-data et un champ de saisie avec multiple. Sans enctype, le serveur ne reçoit tout simplement pas les fichiers; ce n’est pas une erreur PHP, mais une spécification HTTP.
Nommez le champ comme un tableau: fileUpload[]. Les crochets à la fin indiquent à PHP que le serveur reçoit une liste, pas un fichier unique.
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>
Le formulaire ci-dessus permet de sélectionner un nombre quelconque de fichiers en une seule fois dans la boîte de dialogue. Le navigateur les regroupe dans une requête multipart et les envoie au serveur. Aucun JavaScript n’est nécessaire à ce stade.
Étape 2: traitement PHP du tableau de fichiers
Lorsque le formulaire est soumis au serveur, PHP remplit la superglobale $_FILES. Mais la structure diffère de celle d’un téléversement unitaire: les clés name / tmp_name / size / error deviennent des tableaux où l’indice correspond au numéro d’ordre du fichier.
Nous parcourons les indices et enregistrons chaque fichier avec 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 }
Ajoutez ce code au fichier functions.php de votre thème enfant ou au plugin Code Snippets, et le formulaire de l’étape 1 commencera à accepter les fichiers.
Notez que basename() supprime le chemin du nom de fichier. Cela protège contre les attaques de traversée de répertoire: un attaquant pourrait envoyer ../../wp-config.php comme nom. basename() ne conserve que wp-config.php, mais la couche de défense suivante doit rester la validation de l’extension.
Étape 3: vérification du type et de la taille du fichier
Vous ne pouvez pas accepter «tel quel» tout ce que le navigateur envoie. Protection minimale: une liste blanche d’extensions et une limite de taille.
Vérification de l’extension avec pathinfo() et le drapeau PATHINFO_EXTENSION. Convertissez en minuscules et comparez avec les extensions autorisées:
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 }
Le contrôle de la taille est encore plus simple; la valeur size en octets est déjà dans $_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 }
Placez les deux vérifications avant move_uploaded_file() dans la même boucle. Les fichiers qui échouent à la validation n’atteindront pas le disque. Collectez les erreurs dans un tableau et, après le traitement, affichez à l’utilisateur une liste indiquant quel fichier a échoué et pourquoi. Sans cela, les utilisateurs verront simplement un résultat vide et partiront en pensant que le formulaire est cassé. Pour chaque fichier rejeté, indiquez le nom et la raison, par exemple «photo.png: type invalide» ou «archive.zip: taille dépassée».
Étape 4: renommer les fichiers pendant le téléversement
Si deux utilisateurs téléversent photo.jpg, le second fichier écrasera le premier. La solution consiste à générer un nom unique lors de l’enregistrement. L’approche la plus simple: remplacez les espaces par des tirets bas et ajoutez 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 }
L’horodatage time() garantit l’unicité à la seconde près. Sur des projets à fort trafic, remplacez-le par uniqid() ou un UUID: deux requêtes simultanées dans la même seconde suffisent à provoquer une collision et à perdre l’un des fichiers. De plus, time() révèle la date de téléversement dans le nom du fichier, ce qui est parfois indésirable. Un UUID issu de ramsey/uuid élimine complètement ces deux problèmes.
Étape 5: téléversement avec des champs supplémentaires pour chaque fichier
Parfois, vous devez associer un titre, une description ou une catégorie à chaque fichier. Au lieu d’une seule paire «titre + fichier», vous en créez plusieurs, en nommant les champs comme des tableaux:
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>

Côté serveur, $_POST['title'] et $_FILES['fileUpload']['name'] sont des tableaux parallèles. L’indice 0 dans l’un correspond à l’indice 0 dans l’autre. Le traitement utilise la même boucle, mais avec une liaison au titre:
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 }
Assurez-vous que le nombre de champs title[] correspond au nombre de champs fileUpload[] dans le formulaire. Si l’utilisateur ajoute des champs dynamiquement via JavaScript, tous les indices doivent être consécutifs, sans trous. PHP ne réduit pas automatiquement les tableaux épars, donc $_POST['title'][5] peut exister alors que $_FILES['fileUpload']['name'][5] n’existe pas.
En pratique, cette approche couvre une bonne moitié des demandes clients: galeries d’images avec légendes, téléversements de documents avec étiquettes, candidatures avec portfolios. Le même principe de tableaux parallèles fonctionne pour la description, la catégorie, l’ordre et tout autre champ.
⁉️🤔 Foire aux questions
Pourquoi le fichier n’apparaît-il pas dans $_FILES alors que le formulaire a bien été soumis?
Neuf fois sur dix, il manque
enctype="multipart/form-data"dans la balise<form>. Sans cela, le navigateur envoie les données en texte brut et PHP ne remplit pas$_FILES. Vérifiez aussipost_max_sizeetupload_max_filesizedansphp.ini; dépasser l’un ou l’autre donne un tableau vide, sans erreur visible.
Comment limiter la taille totale de téléversement pour une seule requête?
La directive
post_max_sizedansphp.initronque l’intégralité du corps de la requête POST, fichiers et champs texte compris. La valeur par défaut selon la documentation PHP est de 8 Mo. Fixez-la à une valeur supérieure àupload_max_filesizeavec une marge pour l’encodage multipart (environ 20%). Pour des chargements groupés, augmentez les deux paramètres:upload_max_filesize = 20Metpost_max_size = 25M.
Peut-on téléverser des fichiers de manière asynchrone avec JavaScript?
Oui. Utilisez
FormDataetfetchavecmethod: 'POST'. Ajoutez les champsfileUpload[]viaformData.append('fileUpload[]', file)pour chaque fichier sélectionné. Ne définissez pas l’en-têteContent-Typemanuellement; le navigateur positionnera automatiquementmultipart/form-dataavec la bonne limite de séparation. Pour un retour visuel, utilisez une barre de progression viaXMLHttpRequest.upload.onprogress, carfetchn’expose pas la progression directement.
Qu’en est-il de la sécurité: un attaquant téléverse un script PHP au lieu d’une image?
La vérification de l’extension de l’étape 3 est la première ligne de défense. Deuxième ligne: stockez les fichiers téléversés EN DEHORS du répertoire racine du site ou dans un dossier doté d’un
.htaccessqui désactive l’exécution (php_flag engine off). Troisième ligne: renommez les fichiers lors de l’enregistrement (étape 4); même sishell.phppasse le filtre d’extension, il devientshell_1718300000.php, ce qui n’est pas dangereux sans droits d’exécution. Quatrième ligne: vérifiez le type MIME non pas depuis$_FILES['type'](envoyé par le navigateur, facilement falsifiable), mais viafinfo(l’extension Fileinfo intégrée à PHP), qui lit la signature du fichier.
Ce que cette approche apporte: le tableau complet
Vous avez construit un système de téléversement qui gère trois scénarios clés en main: des fichiers groupés avec un seul bouton, un téléversement unitaire avec validation, et l’appairage «fichier + métadonnées» via des tableaux parallèles. Le code fait environ trente lignes pour chaque étape, rien de superflu.
Si vous travaillez sous WordPress, placez le gestionnaire dans le functions.php de votre thème enfant ou dans le plugin Code Snippets. En PHP pur, placez simplement le script de traitement à la cible d’action du formulaire.
Le code de cet article couvre la plupart des tâches de téléversement courantes. Les scénarios restants (téléversement fragmenté pour les gros fichiers, envoi direct vers un stockage compatible S3, et glisser-déposer avec aperçu) seront traités dans des articles séparés. Indiquez en commentaire lequel est pertinent pour votre projet, et nous le prioriserons.



