Le modèle objet de PHP (deuxième partie)

Alexandre Niveau
GREYC — Université de Caen
Adapté du cours de Jean-Marc Lecarpentier
TODO: * traits * namespaces * stdclass ? * iterable * closure

Affichage debug d'un objet


<?php

class Toto {
    private 
$x false;
    protected 
$y "";
    public 
$z null;
    function 
methode() { }
}

echo 
"---- var_export ----\n";
var_export(new Toto());
echo 
"\n";

echo 
"---- print_r ----\n";
print_r(new Toto());
echo 
"\n";

echo 
"---- var_dump ----\n";
var_dump(new Toto());
echo 
"\n";
---- var_export ----
Toto::__set_state(array(
   'x' => false,
   'y' => '',
   'z' => NULL,
))
---- print_r ----
Toto Object
(
    [x:Toto:private] => 
    [y:protected] => 
    [z] => 
)

---- var_dump ----
/users/ensweb/www-prod/pres/oophp/demo/exPrintDebug.php:19:
class Toto#3 (3) {
  private $x =>
  bool(false)
  protected $y =>
  string(0) ""
  public $z =>
  NULL
}


Mot clé final

  • final pour une méthode : la méthode ne peut pas être redéfinie dans une classe dérivée
  • final pour une classe : la classe ne peut pas être dérivée

<?php
class finalEx {
  private 
$donnee "exemple";
  
  final function 
maj() {
    return  
strtoupper($this->donnee);
  }
}

final class 
finalExtend extends finalEx {
  
  
/* Fatal error : impossible de redéfinir maj()
  public function maj() {
    return  $this->donnee;
  } */
}

/* Fatal error : impossible de dériver finalExtend
class erreur extends finalExtend {
  // impossible
}
*/

$class = new finalEx();
echo 
$class->maj();
echo 
"\n";
$classExtend = new finalExtend();
echo 
$classExtend->maj();


?> 
EXEMPLE
EXEMPLE 

Classe et méthode abstraite

  • Mot clé abstract
  • Une classe abstraite ne peut pas être instanciée
  • Une méthode abstraite définit sa signature, pas son implémentation
  • Une méthode abstraite doit être dérivée avec une visibilité inférieure

<?php

abstract class AbstractClass
{
   
// Force la classe étendue à définir cette méthode
   
abstract protected function getValue();

   
// méthode commune
   
public function printOut() {
     print 
$this->getValue();
   }
}

class 
ConcreteClass1 extends AbstractClass
{
   protected function 
getValue() {
     return 
"ConcreteClass1";
   }
}

class 
ConcreteClass2 extends AbstractClass
{
   protected function 
getValue() {
     return 
"ConcreteClass2";
   }
}

$class1 = new ConcreteClass1;
$class1->printOut();

$class2 = new ConcreteClass2;
$class2->printOut();


?> 
ConcreteClass1ConcreteClass2 

Interfaces

  • Permettent de créer du code spécifiant les signatures des méthodes
  • Sans avoir à définir leur implémentation
  • Définir avec le mot clé interface
  • Une classe implémente une interface :
    class maClasse implements monInterface
  • Existence d'interfaces "internes" prédéfinies dans PHP5

Déclaration du type des paramètres

  • « Type hinting » de PHP 5 :
    • On peut déclarer un type pour les paramètres des fonctions
    • Limités à des noms de classes (et array et callable)
    • déjà très utile notamment avec des interfaces
    • function MaFonction(MaClasse $param) : $param doit être une instance de la classe MaClasse
    • Vérification faite à l'exécution. Erreur fatale (catchable…) émise si le paramètre n'est pas une instance de la classe indiquée
  • En PHP 7, on peut utiliser aussi des types primitifs, comme int ou string : voir la liste
  • Attention : par défaut, une TypeError est envoyée si le type n'est pas respecté, sauf si la conversion est possible !
  • il faut ajouter declare(strict_types=1); pour bénéficier du « typage strict », qui renverra bien une TypeError dans tous les cas où le paramètre donné ne respecte pas la déclaration. Sauf pour les int, qui pourront toujours être convertis en float.

Déclaration du type de retour

  • On peut aussi depuis PHP 7 déclarer un type de retour pour les fonctions et méthodes
  • Syntaxe :
    function toto(): float {
        return 3.14;
    }
    
  • On y met les mêmes types que pour les paramètres, avec void en plus
  • Même piège du « mode strict » non activé par défaut (la valeur de retour, par défaut, est simplement convertie si c'est possible)

Types nullifiables

  • Très souvent en PHP les paramètres et les valeurs de retour sont mises à null pour certains cas particuliers (par exemple des erreurs)
  • ennuyeux si on veut typer la fonction
  • Depuis PHP 7.1, on peut utiliser des nullable types, en ajoutant un point d'interrogation au début du type : par ex function titi(?int $x)
  • Signifie qu'on attend un int, ou null

Héritage vs composition

Un principe important en POO est de favoriser la composition plutôt que l'héritage pour mutualiser du code

En effet l'héritage a des conséquences pénibles, notamment le fait qu'à partir du moment où on a sous-classé une classe, il devient difficile de la faire évoluer (tout changement a des conséquences sur les classes filles)

cela contrevient à la modularisation du code — les classes sont trop dépendantes les unes des autres, alors que le but de la POO est plutôt d'essayer de créer des « boîtes noires » dont le comportement interne n'a pas d'influence à l'extérieur

En utilisant la composition, on garde l'indépendance entre les classes

le prix à payer est une syntaxe un peu plus lourde dans la classe, et l'obligation parfois d'écrire des méthodes de « délégation »

ex autom cell: comportement en fonction de la règle, en fonction de l'affichage voulu

Traits

Les traits de PHP sont (principalement) un moyen de faire de la composition, sans les inconvénients

trait MonTrait {
    function uneMethodeUtile() {
}

class Toto {
    use MonTrait;
}
Dans l'exemple ci-dessus, la classe Toto contient la méthode uneMethodeUtile, comme si elle avait hérité de MonTrait, sauf qu'il n'y a pas eu d'héritage :
  • Toto peut très bien étendre une autre classe
  • Toto peut utiliser d'autres traits, pas de problème d'héritage multiple (en cas de conflit de nommage, il faut obligatoirement définir explicitement la méthode concernée dans Toto
  • On peut utiliser une même méthode dans plusieurs classes, sans qu'elles soient liées par le typage
Les inconvénients de la composition disparaissent, puisque tout se passe comme si la méthode était définie dans la classe :
  • syntaxe simple
  • pas besoin d'écrire des méthodes de délégation

Méthodes magiques

  • Méthodes __sleep et __wakeup : code à exécuter lors de la sérialisation et désérialisation d'un objet
  • Utile pour les ressources (connection BDD, ressource fichier)
  • Méthodes __get __set __call pour gérer la surcharge objet (voir le manuel)
  • à utiliser pour gérer les erreurs, pas « pour de vrai » ! peu efficace + code moins lisible, moins analysable par des outils + leaky abstraction, voir un exemple dans la doc

Exemple du cours