Le modèle objet de PHP (première partie)

Alexandre Niveau
GREYC — Université de Caen
Adapté du cours de Jean-Marc Lecarpentier
TODO: * changer les exemples, qui sont encore compliqués pour être vraiment utilisables en CM * Carre extends Rectangle : mauvais exemple; trouver un mieux, ou parler explicitement des problèmes (dépend de l'API — si les instances sont immutables ça ne pose pas de problème)

Programmation objet en PHP

  • La programmation objet a été ajoutée à PHP3, améliorée dans PHP4, et complètement revue dans PHP5
  • Syntaxe et fonctionnement lourdement inspirés de Java
  • Remarque : les variables d'instance, appelées attributs en Java, sont appelées « propriétés » en PHP

Une classe en PHP5

  • Définition et utilisation d'une classe :
    
    <?php
    class Rectangle {
      private 
    $longueur;
      private 
    $largeur;

      public function 
    __construct($L$l) {
        
    $this->longueur $L;
        
    $this->largeur $l;
      }

      public function 
    getLongueur() {
         return 
    $this->longueur;
      }
      public function 
    getLargeur() {
         return 
    $this->largeur;
      }
    }

    $c = new Rectangle(52);
    echo 
    "Dimensions : {$c->getLongueur()}x{$c->getLargeur()}\n";
    echo 
    "Erreur fatale :\n";
    echo 
    "Dimensions : {$c->longueur}x{$c->largeur}";

Pièges des objets

Il faut obligatoirement utiliser $this dans une classe pour faire référence aux propriétés et méthodes
<?php
class Toto {
    public 
$x 3;
    function 
afficherX() {
        echo 
$x;  // (au lieu de $this->x)
    
}
}
$toto = new Toto();
$toto->afficherX();  // lève une Notice: undefined variable $x
Lors de la déclaration de la visibilité des propriétés, on leur met des $, mais pas ailleurs. Cependant, mettre un $ n'est pas une erreur de syntaxe, ça fait juste autre chose que ce qu'on voudrait…
<?php
class Toto {
    public 
$x 3;
}
$toto = new Toto();
echo 
$toto->x;  // affiche 3
echo $toto->$x;  // lève une Notice: undefined variable $x

$variable "x";
echo 
$toto->$variable;  // affiche 3 : $toto->$variable est interprété comme $toto->x !
Un foreach sur un objet parcourt ses propriétés :
<?php
class Toto {
    public 
$x 3;
    public 
$y 'coucou';
}
$toto = new Toto();
foreach (
$toto as $k => $v) {
    echo 
"clef $k, valeur $v\n";
}
// résultat :
//     clef x, valeur 3
//     clef y, valeur coucou

Constructeur, destructeur, conversion en chaîne

  • Fonction __construct()
  • Fonction __destruct() : appelée lorsque l'instance est détruite (utilisation de unset, ou fin de l'exécution du script)
  • Fonction __toString() : appelée lorsque l'objet doit être converti en chaîne de caractères
  • Attention au double underscore du constructeur, crée des bugs difficiles à trouver !

<?php

class Point {
  public 
$x;
  public 
$y;
  public function 
___construct($x$y) {
    
$this->$x;
    
$this->$y;
  }

  public function 
__toString() {
    return 
"({$this->x}{$this->y})";
  }
}

$p = new Point(32);
echo 
$p;
(, )

Membres statiques

  • Utilisation du mot clé static
  • Une propriété statique a la même valeur pour toutes les instances de la classe
  • Une méthode statique peut être appelée sans avoir instancié la classe
  • Différences avec Java :
    • pas la même syntaxe pour l'accès aux propriétés et méthodes statiques : double deux-points à la place de la flèche
    • on n'appelle pas les propriétés et méthodes statiques avec $this, mais avec le mot-clef self, qui représente « la classe de l'instance courante »

<?php

// Exemple de classe avec des membres statiques

class Toto {
  private static 
$compteur 0;
  private 
$dummy;

  public function 
__construct($contenu) {
    
$this->dummy $contenu;
    
self::$compteur++;
  }

  public function 
printInstanceStats() {
    
// $this->compteur n'est pas défini => donne une chaine vide
    
return $this->dummy " et " $this->compteur "\n";
  }

  public static function 
printClassStats() {
    
//$this->dummy = "toto";  // Fatal error
    
return "Il y a actuellement " self::$compteur " instances de la classe Toto\n";
  }

  public function 
__destruct() {
    echo 
"Destruction d'une instance\n";
    
self::$compteur--;
  }
}

echo 
Toto::printClassStats();

$A = new Toto("instance A");
echo 
"\$this->compteur n'est pas défini => donne une chaine vide et une Notice\n";
echo 
"A : " $A->printInstanceStats();
echo 
Toto::printClassStats();

$B = new Toto("instance B");
echo 
"\$this->compteur n'est pas défini => donne une chaine vide et une Notice\n";
echo 
"B : " $B->printInstanceStats();
echo 
Toto::printClassStats();

echo 
"unset(\$A)\n";
unset(
$A);
echo 
Toto::printClassStats();

unset(
$B);
Il y a actuellement 0 instances de la classe Toto
$this->compteur n'est pas défini => donne une chaine vide et une Notice
A : instance A et 
Il y a actuellement 1 instances de la classe Toto
$this->compteur n'est pas défini => donne une chaine vide et une Notice
B : instance B et 
Il y a actuellement 2 instances de la classe Toto
unset($A)
Destruction d'une instance
Il y a actuellement 1 instances de la classe Toto
Destruction d'une instance

Constante d'un objet

  • Définition avec const
  • Comme pour les propriétés statiques, accès avec ::

<?php

class MyClass {
  const 
ma_constante 'toto';
  
  public function 
showConstant() {
    echo  
"Voici ma constante: " .self::ma_constante "\n";
  }
  
  public function 
chgeConstant() {
        
// Fatal error 
        // self::ma_constante = "test d'erreur";
  
}
}

$obj = new MyClass();

$obj->showConstant();

echo 
"MyClass::ma_constante : " MyClass::ma_constante "\n";  // Fonctionne
echo "\$obj->ma_constante : " $obj->ma_constante "\n";  // Ne fonctionne pas
echo "\$obj::ma_constante : " $obj::ma_constante "\n"// Marche avec PHP > 5.3.0 

Dérivation

  • Définition d'une classe dérivée avec extends
    
    <?php
    class Rectangle {
      protected 
    $longueur;
      protected 
    $largeur;

      public function 
    __construct($longueur$largeur) {
        
    $this->longueur $longueur;
        
    $this->largeur $largeur;
      }

    }

    class 
    Carre extends Rectangle {

      public function 
    __construct($largeur) {
        
    parent::__construct($largeur$largeur);
      }
  • Contrairement à Java, constructeurs et destructeurs sont hérités : si non définis dans une sous-classe, ce sont ceux du parent qui sont utilisés
  • En revanche, s'ils sont redéfinis, ils n'appellent pas automatiquement ceux du parent : il faut le faire explicitement avec parent::__construct()

Late static binding

  • Il existe une alternative à self : static
  • Permet de résoudre « tardivement » une référence à un champ statique
  • static::toto n'est pas forcément le toto de la classe courante, mais celui de la classe qui a effectivement été instanciée à l'exécution
  • voir la doc
  • permet de faire de l'héritage sur les méthodes/propriétés statiques…

Visibilité des membres

  • 3 niveaux :
    • public : accessible de partout
    • protected : accès limité à la classe et ses classes dérivées
    • private : accès seulement dans la classe

<?php
class MyClass
{
    public 
$public 'Public';
    protected 
$protected 'Protected';
    private 
$private 'Private';

    function 
printHello()
    {
        echo 
$this->public;
        echo 
$this->protected;
        echo 
$this->private;
    }
}

$obj = new MyClass();
echo 
$obj->public// Works
//echo $obj->protected; // Fatal Error
//echo $obj->private; // Fatal Error
$obj->printHello(); // Shows Public, Protected and Private


// classe dérivée
class MyClass2 extends MyClass
{
    
// We can redeclare the public and protected method, but not private
    
protected $protected 'Protected2';

    function 
printHello()
    {
        echo 
$this->public;
        echo 
$this->protected;
        echo 
$this->private;
    }
}

$obj2 = new MyClass2();
echo 
$obj2->public// Works
echo $obj2->private// Undefined
//echo $obj2->protected; // Fatal Error
$obj2->printHello(); // Shows Public, Protected2, not Private
PublicPublicProtectedPrivatePublicPublicProtected2

Encapsulation des données

  • Données membres non visibles de l'extérieur
  • Utilisation d'accesseurs et de mutateurs

<?php

class Rectangle {
  protected 
$longueur;
  protected 
$largeur;

  public function 
__construct($longueur$largeur) {
    
$this->longueur $longueur;
    
$this->largeur $largeur;
  }

  
// Accesseurs 

  // Getters
  
public function getLongueur() { return $this->longueur; }
  public function 
getLargeur() { return $this->largeur; }

  
// Setters
  
public function setLongueur($longueur) { $this->longueur $longueur; }
  public function 
setLargeur($largeur) { $this->largeur $largeur; }

}

class 
Carre extends Rectangle {

  public function 
__construct($largeur) {
    
parent::__construct($largeur$largeur);
  }

  public function 
setLargeur($largeur) {
    
parent::setLongueur($largeur);
    
parent::setLargeur($largeur);
  }
}


echo 
"Définition d'un rectangle 5x2 :\n";
$R = new Rectangle(52);
// echo $R->longueur; produit Fatal error
// Utiliser les accesseurs :
echo "Longueur : " $R->getLongueur() . "\n";
echo 
"Largeur : " $R->getLargeur() . "\n";

echo 
"Définition d'un carré de largeur 4\n";
$C = new Carre(4);
echo 
"Largeur :  " $C->getLargeur() . "\n";

echo 
"Changement de la largeur : \n";
$C->setLargeur(8);
echo 
"Largeur :  " $C->getLargeur() . "\n";
echo 
"Longueur :  " $C->getLongueur() . "\n";
Définition d'un rectangle 5x2 :
Longueur : 5
Largeur : 2
Définition d'un carré de largeur 4
Largeur :  4
Changement de la largeur : 
Largeur :  8
Longueur :  8

Au passage, noter le mot-clef parent pour appeler une méthode (ou le constructeur) de la superclasse

Exceptions : principe

  • Quand on rencontre un problème dans son programme, au lieu de simplement l'arrêter, on envoie une exception
  • L'envoi d'une exception arrête l'exécution du code en cours
  • L'intérêt est qu'il est possible de récupérer des exceptions qui ont été envoyées par une partie de son programme, afin de traiter la source du problème (si c'est possible)
  • Une exception non récupérée remonte à la fonction appelante, qui elle-même peut avoir mis en place une récupération, etc.
  • Si l'exception remonte « jusqu'en haut », le programme s'arrête avec une
    Fatal error: Uncaught exception

Renvoyer une exception

  • throw new Exception("Houston, we have a problem");
  • Classe Exception prédefinie dans PHP5
  • Méthodes ("final") :
    • getMessage() : message d'erreur de l'exception
    • getCode() : code d'erreur de l'exception
    • getFile() : fichier dans lequel l'exception a été renvoyée
    • getLine() : n° de ligne où l'exception a été renvoyée
    • getTrace() : tableau contenant la trace
    • getTraceAsString() : chaîne de caractères avec la trace

Essayer du code

  • Pour pouvoir récupérer des exceptions, il faut placer le code PHP à exécuter dans un bloc try
  • Tout envoi d'exception dans ce bloc interrompt l'exécution…
  • … mais le programme passe alors à la gestion de l'exception, qui suit

Attraper l'exception

  • Un bloc try doit être suivi d'un bloc catch
  • Ce bloc "attrape" les exceptions éventuellement qui ont été renvoyées dans le bloc try
  • try {
    // code à exécuter
    }
    catch (Exception $e) {
       echo "Erreur, exception renvoyée : " . $e->getMessage();
    }
    

Personnaliser ses exceptions

  • Dériver la classe Exception
  • Permet de créer différents types d'exceptions en utilisant plusieurs blocs catch
  • Une exception non gérée dans un bloc catch est passée au bloc suivant