Cours PHP - leçon 4 - Travail avec les images, bibliothèque GD2.
Dans les leçons précédentes, nous avons appris à écrire des requêtes vers la base de données, donc maintenant nous allons moins nous concentrer sur comment écrire des requêtes, mais plutôt nous exercer à les écrire. Nous allons aussi combiner l’écriture des requêtes avec l’apprentissage d’autres fonctionnalités de PHP, en commençant par le traitement des images. Dans une leçon précédente, nous avons déjà chargé des fichiers, nous avons même une table Files pour les fichiers téléchargés. Chargons maintenant des images dans cette même table. Mais d'abord, il faut ajouter un champ pour le téléchargement d’image dans le formulaire de création de contenu.
$content .= '<label for="bodytext">Message :</label><br />'; $content .= '<textarea name="bodytext" id="bodytext"></textarea><br />'; $content .= '<label for="bodytext">Joindre un fichier :</label><br /><input type="file" name="filename"><br />'; // champ upload fichier $content .= '<label for="bodytext">Joindre une image :</label><br /><input type="file" name="image">'; // champ upload image
Ensuite, il faut écrire le traitement des fichiers, mais d’abord ajoutons une colonne dans la table Messages pour stocker le fid des images, tandis que les données des images seront stockées dans la table Files, comme pour les autres fichiers. J’ai nommé cette colonne image_fid, de type INT(11) et NULL par défaut. Maintenant, insérons le traitement du formulaire dans la méthode write() :
if($_FILES["image"]["size"] > 1024*3*1024){ echo ("La taille du fichier dépasse trois mégaoctets"); exit; } if(is_uploaded_file($_FILES["image"]["tmp_name"])){ // Vérifie si un fichier a été téléchargé // Si le fichier a été téléchargé avec succès, on le déplace // du répertoire temporaire vers le répertoire final move_uploaded_file($_FILES["image"]["tmp_name"], "./files/".$_FILES["filename"]["name"]); } else { echo("Erreur de téléchargement du fichier"); } $sql = 'INSERT INTO Files (filename, filepath, filemime, filesize, timestamp) VALUES ("'. $_FILES['image']['name'] . '", "files/' . $_FILES['image']['name'] . '", "'. $_FILES['image']['type'] .'", '. $_FILES['image']['size'] .', '. time() . ')'; mysql_query($sql);
Ce traitement ne diffère pas du téléchargement d’un fichier classique, bien que dans une prochaine leçon, nous verrons comment vérifier le type de fichier chargé pour éviter que des documents PDF ne soient téléchargés déguisés en images.
Le traitement est écrit, deux fichiers seront donc téléchargés, dont un est une image. Voici le tableau $_POST et $_FILES obtenu en affichant avec print_r() :
Array ( [title] => test [bodytext] => asdfasf ) Array ( [filename] => Array ( [name] => ip.txt [type] => text/plain [tmp_name] => Y:\tmp\php2C5.tmp [error] => 0 [size] => 13 ) [image] => Array ( [name] => Coat_of_arms_of_Vladimiri_Oblast.png [type] => image/png [tmp_name] => Y:\tmp\php2C6.tmp [error] => 0 [size] => 393743 ) )
Il faut aussi modifier la requête d’insertion du message :
$sql = 'INSERT INTO Messages (title, bodytext, created, fid, image_fid) VALUES ("'. $p["title"] . '", "' . $p["bodytext"] . '", ' . time() . ',LAST_INSERT_ID()-1, LAST_INSERT_ID())';
Pour déterminer le fid du fichier classique et de l’image, j’utilise la fonction LAST_INSERT_ID(), puisque le fichier classique est inséré avant l’image, je soustrais donc un à l’id dernier.
Maintenant que les images sont insérées dans la base, on peut les afficher. Pour cela, créons une classe dédiée, nommée simpleImage. Créons un fichier simpleImage.php dans le dossier class et écrivons le code de la classe :
<?php class simpleCMS { } ?>
Nous avons créé une nouvelle classe pour gérer les images, il faudra maintenant créer des méthodes pour travailler avec. Mais d’abord, parlons des types de propriétés dans les classes PHP.
Propriétés de classe en PHP 5
En PHP4, c’était simple : les propriétés de classe étaient définies avec var. En PHP5, on a plus de liberté pour définir les propriétés. Commençons par l’opérateur public, que nous avons déjà utilisé.
Public
Avec public, on déclare une propriété de classe accessible publiquement. On peut donc modifier cette propriété sur un objet créé en lui assignant une valeur, par exemple :
<?php $obj = new simpleCMS(); $obj->db = 'newDB'; ?>
On accède ainsi à la propriété de l’objet, car elle a été déclarée publique.
Protected
L’opérateur protected signifie que la propriété ou méthode est accessible uniquement à l’intérieur de la classe ou de ses classes dérivées (nous parlerons plus tard de l’héritage). Si on déclare la propriété db comme protected et qu’on tente d’y accéder directement, une erreur surviendra :
simpleCMS.php ... protected $db = 'testDB'; ... index.php ... $obj = new simpleCMS(); $obj->db = 'newDB'; ...
Ajoutons maintenant une variable protégée path (chemin du fichier), pour l’utiliser dans les méthodes de la classe. Pour passer ce chemin au fichier, nous utiliserons un constructeur.
Constructeur de classe
Le constructeur sert à initialiser un objet en PHP, par exemple en lui passant le chemin du fichier image. Le constructeur est une méthode dont le nom commence par deux underscores :
class simpleCMS { protected $path; public function __construct($image){ $this->path = $image; } }
On passe donc le chemin du fichier via le paramètre du constructeur et on l’enregistre dans la propriété protégée path. Il faut maintenant modifier la méthode public_display de simpleCMS pour afficher les données d’image.
if(!empty($row['image_fid'])){ $sql = "SELECT * FROM Files WHERE fid=".$row['image_fid']; $image_query = mysql_query($sql) or die(mysql_error()); $image = mysql_fetch_array($image_query); $content .= '<div class="image">'; $image = new simpleImage($image['filepath']); $content .= $image->scale(100,100); $content .= '</div>'; unset($image); }
On récupère donc le chemin vers le fichier depuis la base. Maintenant, écrivons les méthodes pour gérer la taille de l’image. Nous allons créer des fonctions semblables à celles de Drupal :
public function scale($width,$height){ } public function scale_and_crop($width,$height){ }
Commençons par la fonction scale().
public function scale($width, $height){ $path = $this->path; $filepath = explode('/',$this->path); // séparer dossiers et nom fichier $filename = end($filepath); // récupérer dernier élément - nom fichier $filename = substr($filename, 0, -4); // enlever l’extension $filename = str_replace(' ', '_', $filename); // enlever les espaces dans le nom $extenstion = substr($this->path, -4); // vérifier l’extension png, gif, jpg switch ($extenstion){ case '.png': $srcImage = ImageCreateFromPNG( $path ); break; case '.jpg': $srcImage = ImageCreateFromJPEG( $path ); break; case '.gif': $srcImage = ImageCreateFromGIF( $path ); break; default: $srcImage = ImageCreateFromGIF( $path ); break; } // obtenir largeur et hauteur initiales $srcWidth = ImageSX( $srcImage ); $srcHeight = ImageSY( $srcImage ); // calculer proportions pour garder ratio $ratioWidth = $srcWidth/$width; $ratioHeight = $srcHeight/$height; if( $ratioWidth $ratioHeight){ $destWidth = $srcWidth/$ratioHeight; $destHeight = $height; }else{ $destWidth = $width; $destHeight = $srcHeight/$ratioWidth; } // créer nouvelle image avec taille finale print $destWidth. '<br />' .$destHeight; $destImage = imagecreate( $destWidth, $destHeight); // copier srcImage dans destImage ImageCopyResized( $destImage, $srcImage, 0, 0, 0, 0, $destWidth, $destHeight, $srcWidth, $srcHeight ); // créer jpeg imagejpeg( $destImage, 'files/presets/'.$width.'_'.$height.'_'. $filename . '.jpg' ,100 ); // dossier presets doit exister dans files $newImagePath = 'files/presets/'.$width.'_'.$height.'_'. $filename . '.jpg'; // libérer mémoire ImageDestroy( $srcImage ); ImageDestroy( $destImage ); return '<img src="'. $newImagePath . '" width="'. $destWidth.'" height="'. $destHeight . '" />'; // afficher l’image }
Les fichiers images traités seront stockés dans files/presets, ce dossier presets doit être créé et avoir les droits 777.
Je pense que les commentaires expliquent bien le fonctionnement, passons à scale_and_crop(), qui est très similaire à scale() :
public function scale_and_crop($width, $height){ $path = $this->path; $filepath = explode('/',$this->path); // séparer dossiers et nom fichier $filename = end($filepath); // récupérer dernier élément - nom fichier $filename = substr($filename, 0, -4); // enlever l’extension $filename = str_replace(' ', '_', $filename); // enlever espaces $extenstion = substr($this->path, -4); // vérifier extension png, gif, jpg switch ($extenstion){ case '.png': $srcImage = ImageCreateFromPNG( $path ); break; case '.jpg': $srcImage = ImageCreateFromJPEG( $path ); break; case '.gif': $srcImage = ImageCreateFromGIF( $path ); break; default: $srcImage = ImageCreateFromGIF( $path ); break; } // obtenir largeur et hauteur initiales $srcWidth = ImageSX( $srcImage ); $srcHeight = ImageSY( $srcImage ); // calculer ratios source et cible $ratioSource = $srcWidth/$srcHeight; $ratioNew = $width/$height; if( $ratioSource $ratioNew){ $newWidth = $srcWidth; $newHeight = $srcHeight / $ratioNew * $ratioSource; // calcul hauteur proportionnelle }else{ $newWidth = $srcWidth / $ratioSource * $ratioNew; // calcul largeur proportionnelle $newHeight = $srcHeight; } // créer nouvelle image taille finale $destImage = imagecreate($width, $height); // différence avec scale() // copier srcImage dans destImage imagecopyresampled( $destImage, $srcImage, 0, 0, 0, 0, $width, $height, $newWidth, $newHeight); // créer jpeg imagejpeg( $destImage, 'files/presets/'.$width.'_'.$height.'_'. $filename . '.jpg' ,100 ); // dossier presets à créer $newImagePath = 'files/presets/'.$width.'_'.$height.'_'. $filename . '.jpg'; // libérer mémoire ImageDestroy( $srcImage ); ImageDestroy( $destImage ); return '<img src="'. $newImagePath . '" />'; // afficher image }
Il faut aussi modifier la ligne d’affichage dans la méthode display_public() :
$content .= '<div class="image">'; $image = new simpleImage($image['filepath']); $content .= $image->scale_and_crop(100,100); $content .= '</div>';
Les images seront ainsi carrées et recadrées pour respecter les proportions.
Dans les deux cas, les images sont générées à la volée. La génération d’images prend du temps, donc on stocke généralement dans la base les chemins vers les versions miniatures et on affiche directement ces chemins, sans générer à nouveau.
Nous n’aborderons pas cela dans ce cours, car pour construire notre CMS nous utiliserons Zend Framework, et ces cours PHP ont pour but principal de vous familiariser avec les bases de PHP.