Projet en POO avec php : simplifier la gestion des images

PHP > 5.0

Le problème exposé dans ce sujet a été résolu.

Salut à tous,

Je développe un projet en POO qui consiste à simplifier la gestion des images en php.
L’idée, c’est d’associer une image à un objet de la classe Image.

Ainsi, il serait très facile de réaliser du traitement d’images.
Par exemple, pour redimensionner et assombrir une image, le code serait :

<?php

include 'tools/Image.php';

$image = new Image('image.png');

$image->redimensionner(250, 200);
$image->assombrir(0.6);

// Afficher l'image au navigateur et libérer la mémoire
$image->afficher();
$image->supprimer();

Le problème, c’est que la classe Image ne fonctionne pas et retourne des erreurs :

<?php

class Image
{
    // Attributs
    private $_im;
    private $_format;
    private $_largeur;
    private $_hauteur;

    public function __construct($path)
    {
        if (preg_match('#\.jpg$#i', $path) === 1)
        {
            $_im = imagecreatefromjpeg($path);
            $_format = 'JPEG';
        }
        else if (preg_match('#\.png$#i', $path) === 1)
        {
            $_im = imagecreatefrompng($path);
            $_format = 'PNG';
        }
        else
        {
            throw new Exception('Extension du fichier « '.$path.' » non reconnue.');
        }

        $_largeur = imagesx($_im);
        $_hauteur = imagesy($_im);
    }

    public function afficher()
    {
        if ($_format === 'PNG')
        {
            header('Content-type: image/png');
            imagepng($_im);
        }
        else if ($_format === 'JPEG')
        {
            header('Content-type: image/jpeg');
            imagejpeg($_im);
        }
    }

    public function supprimer()
    {
        imagedestroy($_im);
    }
}


// --- Code principal ---

$image = new Image('image.png');
$image->afficher();
$image->supprimer();

Concrètement :

  • Pourquoi le programme indique que la variable $_format est indéfinie à la ligne 34 ?
  • Comment pourrait-on mieux concevoir ce programme en évitant un seul if par type d’image (lignes 13 à 22) ?
  • Comment transformer les notices et les avertissements en erreurs fatales afin de bloquer le programme si une opération échoue ?

Désolé pour la longueur du message, un bien grand merci à ceux qui répondront (même partiellement) ! :ange:

Hello, je passe en coup de vent. :ange:

  • Pourquoi le programme indique que la variable $_format est indéfinie à la ligne 34 ?
LysPrintemps

Parce qu’elle n’est pas définie. :p

Il faut écrire $this->$_format et non pas $_format.

Edit : A chaque fois que tu appel une variable qui appartient à une classe (me souviens plus du nom…) il faut précéder l’appel par $this->.

+0 -0

Il faut écrire $this->$_format et non pas $_format.

Les variables sont encore indéfinies ! :euh:

<?php

class Image
{
    // Attributs
    private $_im;
    private $_format;
    private $_largeur;
    private $_hauteur;

    public function __construct($path)
    {
        if (preg_match('#\.jpg$#i', $path) === 1)
        {
            $this->$_im = imagecreatefromjpeg($path);
            $this->$_format = 'JPEG';
        }
        else if (preg_match('#\.png$#i', $path) === 1)
        {
            $this->$_im = imagecreatefrompng($path);
            $this->$_format = 'PNG';
        }
        else
        {
            throw new Exception('Extension du fichier « '.$path.' » non reconnue.');
        }

        $this->$_largeur = imagesx($this->$_im);
        $this->$_hauteur = imagesy($this->$_im);
    }

    public function afficher()
    {
        if ($this->$_format === 'PNG')
        {
            header('Content-type: image/png');
            imagepng($this->$_im);
        }
        else if ($this->$_format === 'JPEG')
        {
            header('Content-type: image/jpeg');
            imagejpeg($this->$_im);
        }
    }

    public function supprimer()
    {
        imagedestroy($this->$_im);
    }
}



// --- Code principal ---

$image = new Image('image.png');
$image->afficher();
$image->supprimer();

Cela fonctionne, merci bien ! ^^

Je reste preneur pour les questions suivantes :

  • Comment pourrait-on mieux concevoir ce programme en évitant un seul if par type d’image (lignes 13 à 22) ?
  • Comment transformer les notices et les avertissements en erreurs fatales afin de bloquer le programme si une opération échoue ?

pour supprimer les conditions, je pense qu’un

$arr = array('jpg', 'png', 'jpeg');
$extension = pathinfo($path, PATHINFO_EXTENSION);
if (in_array($extension, $arr)) {
  echo $extension;
}

pourrait aider :) je te laisse modifier $arr pour en faire un tableaux multidimentionel et récupérer les valeurs désire en fonction de l’extension ;)

pour supprimer les conditions, je pense qu’un

$arr = array('jpg', 'png', 'jpeg');
$extension = pathinfo($path, PATHINFO_EXTENSION);
if (in_array($extension, $arr)) {
  echo $extension;
}

pourrait aider :) je te laisse modifier $arr pour en faire un tableaux multidimentionel et récupérer les valeurs désire en fonction de l’extension ;)

Leeroy Jenkins

Si on veut vraiment aller dans le POO à fond, on pourrait même aller plus loin et définir la classe Image comme une classe abstraite, laquelle définirait une méthode abstraite generate() par exemple, à implémenter dans les classes filles, comme ceci :

Diagramme de classe : la classe Image devient abstraite, ImagePNG et ImageJpeg étendent Image
Diagramme de classe : la classe Image devient abstraite, ImagePNG et ImageJpeg étendent Image

L’avantage, c’est que comme ça, tu as un contrôles bien plus grand sur ce que tu reçois dans ton constructeur : dans ImagePNG, tu ne gères que le PNG, de même pour ImageJpeg, et cela sans la moindre condition :)

+0 -0

@Jérôme Deuchnord : pourquoi la classe abstraite Image possède-t-elle une méthode afficher() si cette méthode se retrouve déjà dans les classes filles ?

LysPrintemps

C’est une méthode abstraite, c’est-à-dire que tu définis sa signature, mais pas son corps. PHP comprendra ainsi que c’est une méthode qui doit être implémentée dans les classes filles, sans quoi ton programme plantera (on dit que l’on remplit un contrat) :)

Dans ta classes Image, la méthode afficher() ressemblera donc à ça :

public abstract function afficher();
+0 -0

J’ai été me renseigner un peu sur les classes et méthodes abstraites, mais je ne comprends pas l’utilité des méthodes abstraites : on pourrait s’en passer et se contenter d’implémenter les méthodes (non abstraites) dans les classes filles, non ?

Exemple (testé avec php7) :

<?php

abstract class Image
{
    private $_im;
    private $_format;
    private $_largeur;
    private $_hauteur;

    public function supprimer()
    {
        imagedestroy($this->_im);
    }
}

// ImagePNG est un mot-clef réservé
// pour une fonction GD
class Image_PNG extends Image
{
    public function __construct($path)
    {
        $this->_im = imagecreatefrompng($path);
    }

    public function afficher()
    {
        header('Content-type: image/png');
        imagepng($this->_im);
    }
}



// --- Code principal ---

$image = new Image_PNG('image.png');
$image->afficher();
$image->supprimer();

Dans ton cas, c’est vrai qu’on pourrait s’en passer, mais l’idée à retenir, c’est que ta classe abstraite forme une sorte de "contrat" : une nouvelle classe-fille (disons ImageSvg) ne peut pas étendre ta classe abstraite sans avoir sa propre méthode afficher(). Si tu oublies de le faire, PHP te le signalera, car il considérera que c’est une erreur.

C’est très pratique, car tu peux utiliser ta classe abstraite pour faire par exemple ceci :

function obtenirImageAuHasard(array $images): Image
{
    return $images[rand(0, count($images) - 1)];
}

$image = obtenirImageAuHasard([...]);
// Ici, on sait qu'on a reçu une Image, mais on ne sait pas de quel type exactement
// Mais puisque la méthode est définie dans la classe abstraite, je peux faire ceci directement,
// car je sais qu'elle est forcément implémentée :
$image->afficher();

Si tu ne définis pas la méthode abstraite, il te faut alors vérifier que la méthode est bien implémentée, par exemple avec un instanceof, ce qui est peu élégant.

Une autre utilité, très répandue, des méthodes abstraites, est d’appeler ladite méthode abstraite depuis une méthode qui, elle, a déjà été définie. Voici un exemple un peu bancal, mais qui devrait te permettre de comprendre :

abstract class Livre
{
    private $titre;
    private $auteur;
    private $editions;
    private $nbPages;

    public function seLitDeDroiteAGauche(): boolean
    {
        return $this->estJaponais();
    }

    public abstract function estJaponais(): boolean;

    // Getters et setters ici
}

class Manga extends Livre
{
    // Constructeur ici

    public function estJaponais(): boolean
    {
        return true;
    }
}

$manga = new Manga();
echo $manga->seLitDeDroiteAGauche(); // Affichera '1', ce qui équivaut à la valeur booléenne true

Si tu ne définis pas la méthode abstraite ligne 13, PHP plantera, car il ne trouvera pas la méthode estJaponais() appelée dans la méthode au-dessus. L’intérêt ici, c’est de ne pas redéfinir une méthode dans les classes filles si tu sais que leur contenu ne changera pas ou pas souvent, et ce même si cette méthode a besoin d’une valeur retournée par une méthode dont la logique n’est pas encore connue au moment de l’écriture de la classe abstraite :)

+0 -0

Comment transformer les notices et les avertissements en erreurs fatales afin de bloquer le programme si une opération échoue ?

Selon cette source, il est possible d’utiliser ce code :

<?php

function errHandle($errNo, $errStr, $errFile, $errLine) {
    $msg = "$errStr in $errFile on line $errLine";
    if ($errNo == E_NOTICE || $errNo == E_WARNING) {
        throw new ErrorException($msg, $errNo);
    }
    echo $msg;
}

set_error_handler('errHandle');
Connectez-vous pour pouvoir poster un message.
Connexion

Pas encore membre ?

Créez un compte en une minute pour profiter pleinement de toutes les fonctionnalités de Zeste de Savoir. Ici, tout est gratuit et sans publicité.
Créer un compte