Upload de fichiers Envoyer une image de profil depuis un formulaire

Cette note explique comment permettre à un utilisateur d’envoyer un fichier (par exemple une image de profil) au serveur. Elle s’appuie sur l’architecture MVC des TDs (contrôleur frontal, dossier ressources, …) et peut vous servir pour votre projet.

Changement dans le formulaire

Premièrement, il faut changer la propriété enctype de votre formulaire à multipart/form-data pour autoriser l’envoi de fichier, et bien mettre la méthode à post :

<form method="post" action="controleurFrontal.php" enctype="multipart/form-data">
    <!-- Comme pour les autres formulaires des TDs -->
    <input type="hidden" name="controleur" value="utilisateur">
    <input type="hidden" name="action" value="creerDepuisFormulaire">
    <!-- ... -->
    <input type="file" name="photoProfil" accept="image/png, image/jpeg">
    <!-- ... -->
</form>

L’attribut accept ne fait que filtrer les fichiers proposés par le navigateur : il ne protège de rien, car un client peut envoyer n’importe quel fichier. Toutes les vérifications doivent donc être faites côté serveur.

Référence : Mozilla Developer Network

Traitement du fichier reçu

Référence générale : PHP.net

On récupère les informations sur le fichier reçu avec la variable $_FILES['photoProfil']. Remarquez que photoProfil correspond au name de l’input précédent. Il s’agit d’un tableau associatif contenant notamment :

Attention : Le nom name, l’extension du fichier et le type $_FILES['photoProfil']['type'] sont choisis par le client : il ne faut jamais leur faire confiance. Sinon, un attaquant pourrait par exemple envoyer un fichier piratage.php, puis l’exécuter en allant à son URL, ou bien utiliser un nom comme ../../index.php pour écraser un fichier de votre site.

Voici donc les étapes à suivre, par exemple dans l’action creerDepuisFormulaire de ControleurUtilisateur :

// 1. Vérifier que le fichier a bien été reçu
if (!isset($_FILES['photoProfil']) || $_FILES['photoProfil']['error'] !== UPLOAD_ERR_OK) {
    ControleurUtilisateur::afficherErreur("Erreur lors de l'envoi du fichier");
    return;
}
$fichier = $_FILES['photoProfil'];

// 2. Limiter la taille (ici 2 Mo)
if ($fichier['size'] > 2 * 1024 * 1024) {
    ControleurUtilisateur::afficherErreur("Fichier trop volumineux");
    return;
}

// 3. Déterminer le vrai type du fichier en lisant son contenu
//    (\finfo car nous sommes dans un espace de noms)
$typesAutorises = ["image/jpeg" => "jpg", "image/png" => "png"];
$typeMime = (new \finfo(FILEINFO_MIME_TYPE))->file($fichier['tmp_name']);
if (!array_key_exists($typeMime, $typesAutorises)) {
    ControleurUtilisateur::afficherErreur("Seules les images JPEG et PNG sont acceptées");
    return;
}

// 4. Générer un nom de fichier aléatoire, avec une extension choisie par nous
$nomFichier = bin2hex(random_bytes(16)) . "." . $typesAutorises[$typeMime];

// 5. Déplacer le fichier temporaire vers le dossier ressources/img/profils
$cheminDestination = __DIR__ . "/../../ressources/img/profils/$nomFichier";
if (!move_uploaded_file($fichier['tmp_name'], $cheminDestination)) {
    ControleurUtilisateur::afficherErreur("La copie du fichier a échoué");
    return;
}

// 6. Enregistrer $nomFichier en base de données (par ex. dans une colonne
//    photoProfil de la table utilisateur)

Quelques explications :

Configuration du serveur