Aller au contenu

En pratique

Dans l'exerice blog, nous allons autoriser l'upload d'une image par article.

Si vous le désirez, vous pouvez télécharger la version de départ sur GitHub >>

Veillez à modifier les valeurs dans les fichiers de configuration (config_perso.inc.php et config.inc.php )

Astuce

Pour télécharger les sources,

- cliquer sur le bouton vert `<> Code`
- cliquer sur `Download Zip`

Download souce

Reprenons les étapes une à une :

1 - Modifier le formulaire

2 - Ajouter des contraintes côté client

3 - Ajouter des vérifications côté serveur & traitement

4 - Modifier la table dans la BD

5 - Ajuster les droits sur le dossier uploads

6 - Réaliser l'upload

Modifier le formulaire

Dans le fichier new.php :

  • Ajouter l'input permettant le téléchargement de fichier dans le formulaire
  • Préciser le type d'encodage

Rappel >>

Soluce
<?php 
<form action="<?= nettoyage($_SERVER['PHP_SELF']); ?>" method="post" class="formAdmin" enctype="multipart/form-data">
[...code...]
<input type="file" name="photo_article" >
[...code...]

Contraintes côté client

Les fichiers autorisés seront uniquement des images inférieures à 1Mo.

Dans le fichier new.php, ajoutez ces deux contraintes dans le formulaire.

Rappel >>

💡 Tip: Travailler avec des constantes et centraliser celles-ci dans un fichier

Soluce
<?php 
//déclarer des constantes
const UN_MEGA_EN_OCTETS = 1048576;
const MAX_FILE_SIZE = 1 * UN_MEGA_EN_OCTETS;

[...code...]
<input type="hidden" name="MAX_FILE_SIZE" value="<?= MAX_FILE_SIZE ?>">
<input type="file" name="photo_article" accept="image/*">
[...code...]

Contraintes côté serveur & traitement

Nous allons réaliser différentes contraintes au niveau serveur et traiter le fichier uploadé.

Rappel >>

Fonctions génériques

⚠ Warning: Dans un projet, il arrive fréquemment de devoir réaliser des uploads à partir de pages différentes. Afin d'éviter les redondances, il est préconisé d'implémenter des fonctions génériques ou une classe dédiée.

Dans le dossier php, créez un nouveau fichier nommé utils_upload.inc.php qui contiendra nos fonctions :

Gestion des erreurs de téléchargement

Rappelez-vous, lors de l'upload, le tableau $_FILES contient un clé nommée error Rappel >>

Créez une fonction qui reçoit la valeur de cette clé et qui retourne un message d'erreur adapté. Aidez-vous de ces infos >> .

Voici la signature de la fonction à utiliser :

gestionErreurTelechargement()
<?php 
/**
 * Convertit un code d'erreur de téléchargement en message explicatif. 
 *
 *
 * @param int $codeErreur Code d'erreur du téléchargement.
 *                   - UPLOAD_ERR_NO_FILE : Fichier manquant.
 *                   - UPLOAD_ERR_INI_SIZE : Fichier dépassant la taille maximale autorisée par PHP.
 *                   - UPLOAD_ERR_FORM_SIZE : Fichier dépassant la taille maximale autorisée par le formulaire.
 *                   - UPLOAD_ERR_PARTIAL : Fichier transféré partiellement.
 *                   - Autre : Erreur inconnue.
 *
 * @return string|null Message d'erreur si $codeErreur > 0, sinon null.
 */
 function obtenirMessageErreurTelechargement(int $codeErreur): ?string{}
Astuce

Utiliser un switch ... case >> sur la valeur de l'erreur.

Validité fichier

Créez une fonction qui vérifie la validité d'un fichier uploadé.

Voici la signature de la fonction.

<?php 
/**
 * Vérifie la validité d'un fichier uploadé.
 *
 * @param array $fichier Données du fichier ($_FILES).
 * @param array $extensionsAutorisees Extensions autorisées.
 * @return bool|string True si le fichier est valide, sinon un message d'erreur.
 */
function verifierUpload(array $fichier, array  $extensionsAutorisees): bool|string
{}

Cette fonction va réaliser les vérifications suivantes. En cas d'erreur, personnalisez le message envoyé.

  • vérifier si un fichier a bien été envoyé ( if (empty($fichier['name'])) {)
  • vérifier le code d'erreur (utiliser la fonction (obtenirMessageErreurTelechargement() ))
  • vérifier que l'extension du fichier fait partie de celles attendues.

    • Récupérez l'extension du fichier en utilisant pathinfo

    • exemple : $infosFichier = pathinfo($_FILES['name']);
      $extension = strtolower($infosFichier['extension'] ?? '');

    Explication

    pathinfo : Analyse une chaîne représentant un chemin de fichier et retourne un tableau associatif contenant des informations telles que :

    `dirname` : Le chemin du répertoire.
    
    `basename` : Le nom complet du fichier (nom + extension).
    
    `extension` : L'extension du fichier.
    
    `filename` : Le nom du fichier sans l'extension.
    
    • vérifier que l'extension du fichier fait partie du tableau des extensions autorisées (reçu en paramètre de la fonction)
  • vérifier que la taille du fichier ne dépasse pas la taille autorisée. Rappelez-vous que la taille autorisée a été spécifiée via une constante nommée MAX_FILE_SIZE

Génération nom unique

Créez une fonction qui génére un nom unique, par exemple un identifiant basé sur le temps et le nom du fichier original. Attention, si le nom original comporte plus de 5 caractères, il sera tronqué. Le nom retourné conservera l'extension d'origine. Pour vous aider, utiliser la fonction pathinfo Référence officielle >> Voici la signature de la fonction.

<?php 
/**
 * Génère un nom de fichier, en minuscules, unique en conservant l'extension d'origine.
 * Le nom d'origine (sans espaces) est tronqué à 5 caractères.
 * Retourne false si l'extension est absente.
 *
 * @param string $nom Nom original du fichier.
 * @return string|false Nom de fichier unique ou false.
 */
function genererNomUnique(string $nomOriginal): string|false
{

}
Astuce
  • pathinfo vous permet d'obtenir des infos sous forme d'un tableau associatif
  • vérifier la présence de l'extension (empty)
  • remplacer les espaces blancs (str_replace)
  • tronquer une chaine de caractères (substr)
  • convertir en minuscules (strtolower)
  • générer un code "unique" (uniqid)

Déplacer le fichier

Créez une fonction qui va traiter déplacer le fichier.

Voici sa signature :

<?php 
/**
 * Renomme et déplace le fichier
 *
 * @param array $fichier Données du fichier ($_FILES).
 * @return array{
 *   statut: bool,       // true en cas de succès, false sinon.
 *   filename?: string,  // Clé présente uniquement en cas de succès.
 *   erreur?: string     // Clé présente uniquement en cas d'échec.
 * } Tableau associatif contenant le résultat de l'opération.
 */
function deplacerFichier(array $fichier): array {}

Cette fonction va :

  • générer un nom unique (utiliser la fonction genererNomUnique())

  • déplacer le fichier vers le dossier uploads (utiliser la fonction move_uploaded_file())

Cette fonction deplacerFichier() sera appelée lors de la création ou modification d'un article. Si l'upload se déroule correctement, la requête sql devra enregistrer le nouveau nom de fichier dans la table. Il nous faut donc récupérer ce nom de fichier. Mais si le déplacement de fichier génére une erreur, nous devons récupérer cette erreur pour l'afficher. Comment distinguer si la string retournée est un message d'erreur ou le nom du fichier à stocker dans la table ?

Nous pourrions utiliser des références de variables (comme dans les classes), mais nous allons découvrir une autre technique. [Dans votre projet, vous utiliserez la technique de votre choix.]

Cette fonction va retourner un tableau associatif comprenant les clés suivantes :

  • statut : comporte vrai si l'upload est correctement réalise, faux si une erreur est détectée

  • Si le statut est faux, la clé erreur comportera un message d'erreur

  • Si le statut est vrai, la clé nomFichierServeur comportera le nom du fichier à stocker dans la table

Exemple :

<?php 
 $nomFichierServeur = genererNomUnique($fichier['name']);

if (!$nomFichierServeur) {
    return ['statut' => false, 'erreur' => "Impossible de générer un nom de fichier"];
}

Modifier la table dans la BD

  • Supprimez et recréez les tables + insert via ce lien>>
  • Mettez à jour votre classe Article (attribut image à ajouter, n'oubliez pas que l'attribut doit porter le même nom que la colonne de la table )
  • Modifiez la méthode insertArticle() (requête sql)

⚠ Warning: Ne modifiez toutes vos méthodes ensemble mais travaillez étape par étape.

Modifier les droits sur le dossier uploads

Via un client FTP, modifiez les droits sur le dossier uploads

Rappel >>

Gestion de l'insert

Actuellement, dans le fichier new.php, nous vérifions les titre et contenu.

Ajoutons une vérification pour l'upload. Utilisez le tableau $_FILES. Si un fichier est présent, on appelle la fonction verifierUpload().

Si la vérification génére des erreurs, on crée une entrée dans le tableau des erreurs.

Soluce
<?php 
// Traitement du fichier uploadé
 if (isset($_FILES['photo_article']) && !empty($_FILES['photo_article']['name'])) {
    $extensionsAutorisees = ['jpg','png','gif'];
    $verifUpload = verifierUpload($_FILES['photo_article'],  $extensionsAutorisees);
    if ($verifUpload !== true) {
        $erreurs[] = $verifUpload;
    }

} else {
    $erreurs[] = 'image obligatoire';
}

Warning

Testez votre code.

- Ajouter sans donnée

- Ajouter sans image

- Charger une image trop lourde

- Charger un fichier texte

- Charger une bonne image

Si les messages d'erreur/confirmation n'apparaissent pas, corrigez votre code avant d'aller plus loin !

Si aucune erreur n'est générée, on déplace le fichier à l'aide de la fonction deplacerFichier()

Si la fonction ne génère aucune erreur, on récupère le nom du fichier sinon on crée une entrée dans le tableau des erreurs.

Soluce
<?php 
// Traitement du fichier uploadé
    if (isset($_FILES['photo_article']) && !empty($_FILES['photo_article']['name'])) {
        $verifUpload = verifierUpload($_FILES['photo_article'],  $extensionsAutorisees);
        if ($verifUpload !== true) {
            $erreurs[] = $verifUpload;
        } elseif (empty($erreurs)) {
            $uploadResult = deplacerFichier($_FILES['photo_article']);
            if ($uploadResult['statut']) {
                $nomFichier = $uploadResult['nomFichierServeur'];
            } else {
                $erreurs[] = $uploadResult['erreur'];
            }
        }
    } else {
        $erreurs[] = 'image obligatoire';
    }

Si aucune erreur n'est détectée, on crée un objet Article. N'oubliez pas d'ajouter l'attribut $article->nom_img = $nomFichier;

Warning

Testez votre code. L'insert doit avoir lieu. Si l'insert n'est pas réalisé dans la table, corrigez votre code avant d'aller plus loin ! Ajoutez le lien vers l'image dans la page article.php

Si vous obtenez une erreur de ce type: Warning: move_uploaded_file( /uploads/67d82a01355e0_img_2.jpg): Failed to open stream: No such file or directory in, vérifiez les droits sur le dossier d'uploads, vérifiez le chemin utilisé...

Gestion de la modification

Modifiez la requête dans la classe ArticleRepository

Lorsque nous cliquons sur le bouton Modifier, les données sont affichées dans le formulaire. Nous allons afficher le nom du fichier dans le formulaire.

Ajoutez le nom du fichier dans le formulaire

1
2
3
4
5
<?php 
<input type="file" name="photo_article" accept="image/*">
<?php if (!empty($nomFichier)) { ?>
    <p>Image actuelle : <?= $nomFichier ?></p>
<?php } ?>

Et n'oubliez pas d'assigner la valeur à la variable.

<?php 
// Chargement de l'article en mode modification
if ($id !== false && $id !== null) {
    $article = $articleRepository->getArticleById($id, $messageErreur);
    if ($article) {
        $titre = nettoyage($article->titre);
        $contenu = nettoyage($article->contenu);
        $nomFichier =  nettoyage($article->nom_img);
    } else {
        $messageErreur = "Article introuvable.";
    }
}

Testez votre code => cliquez sur un bouton modifier, supprimer la valeur du titre et valider. Que se passe-t-il ?

Soluce

La page est rechargée et un message d'erreur indique qu'il manque un titre et une image.

Pourquoi ?

Les données affichées dans le formulaire en cas d'erreur sont celles qui étaient présentes dans des champs du formulaire. Le nom du fichier n'est pas stocké dans un input et donc, n'est pas envoyé via la tableau $_POST. Afin de conserver le nom de l'image, nous allons le stocker dans un champ caché.

<?php 
<input type="hidden" name="nomFichier" value="<?= $nomFichier ?>">

N'oubliez pas de stocker la valeur récupérée du formulaire en début de page.

1
2
3
4
<?php 
$titre = nettoyage($_POST['titre'] ?? '');
$contenu = nettoyage($_POST['contenu'] ?? '');
$nomFichier = nettoyage($_POST['nomFichier'] ?? '');

Retestez votre code => modifier uniquement le titre. Que se passe-t-il ? Pourquoi ? Adaptez votre code.

Soluce

L'erreur 'Image obligatoire' apparait s'il n'y a pas de fichier uploadé ( if (isset($_FILES['photo_article']) && !empty($_FILES['photo_article']['name'])) {) Mais en cas de modification, l'utilisateur n'est pas obligé d'uploader un nouveau fichier. Nous allons donc ajouter une condition pour afficher le message d'erreur.

// Traitement du fichier uploadé
if (isset($_FILES['photo_article']) && !empty($_FILES['photo_article']['name'])) {
    $verifUpload = verifierUpload($_FILES['photo_article'],  $extensionsAutorisees);
    if ($verifUpload !== true) {
        $erreurs[] = $verifUpload;
    } elseif (empty($erreurs)) {
        $uploadResult = deplacerFichier($_FILES['photo_article']);
        if ($uploadResult['statut']) {
            $nomFichier = $uploadResult['nomFichierServeur'];
        } else {
            $erreurs[] = $uploadResult['erreur'];
        }
    }
} elseif (empty($nomFichier)) {
    $erreurs[] = 'image obligatoire';
}