Upload d’image en PHP

L’upload d’image est une des fonctions de base d’un site internet moderne, qu’il s’agisse de l’édition du profil avec Avatar, ou de l’ajout d’une photo personnelle sur un réseau social, ou encore l’envoi d’une image sur un site de stockage spécialisé pour forums... etc.

PHP dispose de toutes les fonctions nécessaire au traitement d’un upload, quel qu’il soit, via le multipart/form-data. Mais permettre à un visiteur d’envoyer des fichiers sur votre serveur est un vecteur d’attaque critique. En effet n’importe qui pourrait uploader un fichier malicieux qui, par exemple, effacerait tous les autres fichiers du serveur.

C’est pourquoi l’upload se limite souvent aux images, ou autre type de fichier défini. Nous allons voir dans ce tutoriel comment vérifier qu’un utilisateur upload bien une image valide et du type voulu.

Le formulaire HTML

La première brique de l’édifice, un formulaire HTML tout à fait classique, si ce n’est le enctype="multipart/form-data" dans la balise <form>. Il ne faut pas l’oublier !

  1. <form method="POST" action="" enctype="multipart/form-data">
  2.     <fieldset>
  3.     <legend>Envoi d'image</legend>
  4.         <p>
  5.             <label for="photo">Image : </label>
  6.             <input type="file" name="photo" id="photo" />
  7.         </p>
  8.         <p>
  9.             <input type="hidden" name="MAX_FILE_SIZE" value="<?php echo $taille_max; ?>" />
  10.             <input type="submit" name="envoi" value="Envoyer l'image" />
  11.         </p>
  12.     </fieldset>
  13. </form>

Un formulaire d’upload se compose donc :

  • d’un enctype="multipart/form-data"
  • d’un input de type="file"
  • d’un input de type="hidden", de nom MAX_FILE_SIZE et dont la valeur est en octets. Ce dernier champs sert à PHP pour définir un poids maximal pour les fichiers à uploader.

Récupération de l’image via PHP

Notre image va être contenu dans le tableau $_FILES[’photo’]. Analysons ce que ce tableau contient après un envoi de fichier :

  1. Array
  2. (
  3.     [name] => cover2.jpg
  4.     [type] => image/jpeg
  5.     [tmp_name] => c:/wamp/tmp\phpBF5.tmp
  6.     [error] => 0
  7.     [size] => 50142
  8. )
  • name : Le nom du fichier, tel qu’il était sur l’ordinateur
  • type : Le mime-type du fichier, il est renvoyé par le navigateur et il ne vaut donc mieux pas s’y fier !
  • tmp_name : L’emplacement du fichier dans le tmp/ de PHP (le fichier est donc déjà présent physiquement sur le serveur)
  • error : Un code d’erreur (que nous allons exploiter)
  • size : Le poids du fichier en octets

Le code d’erreur renvoyé par PHP va nous permettre d’effectuer une première série de vérifications. Pour voir les correspondances code d’erreur -> intitulé de l’erreur, un petit tour dans la doc PHP suffit.

  1. switch ($_FILES['photo']['error']) {
  2.                 case 1:
  3.                     $erreurs[] = "Votre image doit faire moins de $taille_ko Ko !";
  4.                     break;
  5.                 case 2:
  6.                     $erreurs[] = "Votre image doit faire moins de $taille_ko Ko !";
  7.                     break;
  8.                 case 3:
  9.                     $erreurs[] = "L'image n'a été que partiellement téléchargé.";
  10.                     break;
  11.                 case 4:
  12.                     $erreurs[] = "Aucun fichier n'a été téléchargé.";
  13.                     break; // Pas de 5, ne pas demander pourquoi ^^ (voir doc PHP)
  14.                 case 6:
  15.                     $erreurs[] = "Un dossier temporaire est manquant.";
  16.                     break;
  17.                 case 7:
  18.                     $erreurs[] = "Échec de l'écriture du fichier sur le disque.";
  19.                     break;
  20.             }

En cas d’erreur on remplis un Array nommé $erreurs, nous allons ainsi pouvoir afficher plusieurs erreurs gracieusement :)

On est maintenant à peu prêt sûr que le fichier à bien été transmis, reste à effectuer les tests de type, d’extension... etc.
Pour cela nous allons définir quelques variables et fonctions dont nous aurons besoin :

  1. function get_extension($nom) {
  2.     $nom = explode(".", $nom);
  3.     $nb = count($nom);
  4.     return strtolower($nom[$nb-1]);
  5. }
  6.  
  7.  
  8. $extensions_ok = array('jpg', 'jpeg');
  9. $typeimages_ok = array(2);
  10.  
  11. $taille_ko = 1024;
  12. $taille_max = $taille_ko*1024;
  13. $dest_dossier = 'img/';

La fonction get_extension est plutôt simple, on explose le nom du fichier à tous les points, et on prend le dernier segment.
On défini ensuite un Array contenant toutes les extensions autorisés, un autre avec les constantes getimagesize autorisé (1 = GIF, 2 = JPG, 3 = PNG, 4 = SWF, 5 = PSD, 6 = BMP, 7 = TIFF (Ordre des octets Intel), 8 = TIFF (Ordre des octets Motorola), 9 = JPC, 10 = JP2, 11 = JPX, 12 = JB2, 13 = SWC, 14 = IFF) et quelques variable clairement identifiable.

getimagesize() est une fonction PHP très utile pour tester une image, elle renvoie pleins d’informations utile, et FALSE si le fichier n’est pas une image valide :

  1. if(!$getimagesize = getimagesize($_FILES['photo']['tmp_name'])) {
  2.             $erreurs[] = "Le fichier n'est pas une image valide.";
  3.         }

C’est avec elle qu’on vérifie le type réel de l’image, en plus de tester l’extension :

  1. if( (!in_array( get_extension($_FILES['photo']['name']), $extensions_ok ))
  2.            or (!in_array($getimagesize[2], $typeimages_ok )))
  3.         {
  4.             $erreurs[] = 'Veuillez sélectionner un fichier de type Jpeg !';
  5.         }

On vérifie ensuite que le poids de l’image est réellement inférieur à nos directives, et si le fichier est vraiment présent sur le serveur :

  1. // on vérifie le poids de l'image
  2.         if( file_exists($_FILES['photo']['tmp_name'])
  3.                   and filesize($_FILES['photo']['tmp_name']) > $taille_max)
  4.         {
  5.             $erreurs[] = "Votre fichier doit faire moins de $taille_ko Ko !";
  6.         }

PHP teste déjà ça pour nous au début, mais ces tests pourraient s’avérer utiles sur un serveur mal configuré ou mal entretenu.

Tous nos tests sont maintenant terminé, nous sommes maintenant sûr à 99% d’avoir bien une image de type Jpeg.

Déplacer le fichier sur le serveur

Notre image se trouve pour le moment dans le tmp/ de PHP, il nous faut la déplacer vers notre répertoire de destination. Pour cela PHP devra avoir les droits d’écriture sur le dossier en question (chmood).

Avant de déplacer notre fichier, nous devons aussi vérifier qu’un fichier du même nom n’existe pas déjà, car autrement PHP l’écraserait sans préavis.

On commence par nettoyer le nom du fichier, afin d’éviter les caractères spéciaux qui ne sont pas très sexy dans une url :

  1. $dest_fichier = basename($_FILES['photo']['name']);
  2.             $dest_fichier = strtr($dest_fichier, 'ÀÁÂÃÄÅÇÈÉÊËÌÍÎÏÒÓÔÕÖÙÚÛÜÝàáâãäåçèéêëìíîïðòóôõöùúûüýÿ', 'AAAAAACEEEEIIIIOOOOOUUUUYaaaaaaceeeeiiiioooooouuuuyy');
  3.             // un chtit regex pour remplacer tous ce qui n'est ni chiffre ni lettre par "_"
  4.             $dest_fichier = preg_replace('/([^.a-z0-9]+)/i', '_', $dest_fichier);
  5.            
  6.             // pour ne pas écraser un fichier existant
  7.             while(file_exists($dest_dossier . $dest_fichier)) {
  8.                 $dest_fichier = rand().$dest_fichier;
  9.             }

Une fois le nom nettoyé, on fait un while(tant que le fichier existe), en ajoutant une partie aléatoire au nom du fichier. C’est peut être pas la meilleure solution, c’est à adapter en fonction de votre application (si c’est pour un avatar, renommer l’image avec l’identifiant unique du membre par exemple).

Nous pouvons maintenant utiliser la fonction move_uploaded_file() pour déplacer notre fichier du tmp/ au dossier voulu :

  1. if(move_uploaded_file($_FILES['photo']['tmp_name'], $dest_dossier . $dest_fichier)) {
  2.                 $valid[] = "Image uploadé avec succés (<a href='".$dest_dossier . $dest_fichier."'>Voir</a>)";
  3.             } else {
  4.                 $erreurs[] = "Impossible d'uploader le fichier.<br />Veuillez vérifier que le dossier ".$dest_dossier." existe avec un chmod 755 (ou 777).";
  5.  
  6.             }

Le code complet

Si vous avez tout suivi, vous êtes maintenant capable de produire le script complet (j’ai volontairement évité de vous coller les blocs de test et autre gestion des erreurs... ), si ce n’est pas le cas, le code complet du script d’upload d’image en php est disponible ici. Il est entièrement fonctionnel et prêt à être utilisé pour une application.

Commentaires, réactions : sur le blog ! Merci.
Dernière mise à jour le 21 Novembre 2007.

Creative Commons License
Tutoriel Upload d'image en PHP est mis à disposition selon les termes de la licence Creative Commons Paternité-Pas d'Utilisation Commerciale 2.0 France par son auteur, Damien ALEXANDRE.


Webdéveloppeur, Intégrateur, Webdesigner

Damien ALEXANDRE, diplômé du DUT SRC (Services et Réseaux de Communications) de l'IUT Michel de Montaigne Bordeaux 3, je suis actuellement développeur PHP et intégrateur XHTML/CSS.

Ici vous pouvez découvrir mes travaux, mes créations et mon curriculum vitae. Pour plus d'informations, vous pouvez également me contacter, et visiter mon blog.

Best skills

  • XHTML/CSS
  • XML
  • JavaScript/DOM/AJAX
  • PHP/MySQL
  • SEO
  • XForms