Synthèse de la Programmation orientée objet en PHP

Quentin Busuttil

Pour réagir au contenu de ce tutoriel, un espace de dialogue vous est proposé sur le forum. Commentez

- Titre : Synthèse de la Programmation orientée objet en PHP

- Auteur : Quentin Busuttil

- Parution : 5 décembre 2015

- Mise à jour : 13 février 2017

- Public visé :

- Licence : Le contenu de cet article est rédigé par Quentin Busuttil et est mis à disposition selon les termes de la Licence Creative Commons Attribution - Pas d'Utilisation Commerciale - Pas de Modification 3.0 non transposé. Les logos Developpez.com, en-tête, pied de page, css, et look & feel de l'article sont Copyright ® 2013 Developpez.com.

Sommaire

- I. Introduction

- I-A. Pseudo variable $this

- I-B. Les types de visibilité

- I-C. Auto chargement des classes

- I-D. Constantes de classe

- I-E. Attributs et méthodes statiques

- I-E-1. Méthodes statiques

- I-E-2. Les attributs statiques

- I-F. POO et BDD

- I-G. Hydrater ses objets

- I-H. L'héritage

- I-H-1. Surcharger les méthodes

- I-H-2. Les contraintes

- I-H-2-a. Classe abstraite

- I-H-2-b. Méthode abstraite

- I-H-2-c. Classes finales

- I-H-2-d. Méthodes finales

- I-I. Les méthodes magiques

- I-I-1. Exemples

- I-I-1-a. __set

- I-I-1-b. __invoke

- I-J. Clonage

- I-K. Comparaisons

- I-L. Interfaces

- I-M. Exceptions

- I-N. Les classes anonymes

- I-O. Réflexion

- II. Note de la rédaction de Developpez.com

I - Introduction

Cet article était jusque là en privé, juste pour mon usage personnel, et puis je me suis dit qu'il serait bête de ne pas en faire profiter les autres (c'est le but d'un blog non ?). Voici donc quelques rappels sur la Programmation orientée objet (POO) en PHP. Bien que j'explique un peu les différentes notions, cet article est une cheatsheet sur la POO. Par conséquent, il n'est pas destiné à ceux qui n'en ont jamais fait (ils seront totalement perdus). En revanche, ceux qui connaissent déjà, mais qui, comme moi, ont quelques trous de mémoire, vous pouvez bookmarquer cet article !

I-A - Pseudo variable $this

C'est une variable qui est disponible dans les méthodes lorsqu'on instancie une classe. Elle représente l'objet qu'on est en train d'utiliser, donc tous ces attributs et ses méthodes.

1. class MaClasse {

2. private $_monAttribut = 'je ne sers à rien';

4. public function changerAttribut() {

5. $this->_monAttribut .= ' et je ne sers toujours à rien !';

6. return $this->_monAttribut;

7. }

8. }

10. $obj = new MaClasse;

11. echo $obj->changerAttribut();

Vous noterez que lorsqu'on appelle un attribut avec $this->, on ne met pas le dollar.

I-B - Les types de visibilité

Ils définissent la portée des attributs et méthodes d'un objet. Il y en a trois :

public

L'attribut ou méthode est visible à partir de n'importe où.

private

L'attribut ou méthode n'est visible qu'à partir de l'intérieur de la classe à laquelle il appartient.

protected

Comme private, mais il étend la visibilité aux classes filles.

I-C - Auto chargement des classes

Il est bon pour l'organisation d'avoir un fichier PHP par classe. Vous êtes certainement au courant, quand on veut utiliser du code d'un fichier A dans un fichier B, on doit inclure le fichier A dans B. Cela s'effectue grâce aux fonctions require ou include. Néanmoins, avec un fichier par classe, vous n'êtes pas sortis de l'auberge ! C'est pourquoi il est possible d'automatiser tout ça.

On part du principe que les noms de vos fichiers respectent une certaine logique. Par exemple, en ce qui me concerne, mes fichiers de classe s'appellent NomDeLaClasse.class.php. On va donc créer une fonction qui permettra d'inclure automatiquement mes classes.

1. function chargerClasse ($classe) {

2. // On inclut la classe correspondant au paramètre passé

3. require $classe . '.class.php';

4. }

OK, jusque là, ça ne sert pas à grand-chose… Mais c'est sans compter sur spl_autoload_register. Cette fonction de PHP permet d'enregistrer une fonction d'autoload. En d'autres termes, à chaque fois que l'on appelle une classe non déclarée, la fonction que vous avez enregistrée via spl_autoload_register sera appelée avec le nom de la classe qui va bien en paramètre !

1. // On enregistre la fonction en autoload

2. // pour qu'elle soit appelée dès qu'on instanciera une classe non déclarée

3. spl_autoload_register ('chargerClasse');

5. $obj = new MaClasseNonDéclarée;

I-D - Constantes de classe

1. class MaClasse {

2. // Déclaration de la constante

3. const MA_CONSTANTE = 20;

4. }

6. // Instanciation de l'objet

7. $obj = new MaClasse (MaClasse::MA_CONSTANTE);

Les « doubles deux points » sont ce qu'on appelle l'opérateur de résolution de portée. Comme une constante n'appartient pas à un objet, mais à une classe, on ne peut pas accéder à ces valeurs avec l'opérateur ->.

I-E - Attributs et méthodes statiques

Les méthodes statiques sont des méthodes qui sont liées à une classe et non à un objet. Par conséquent, l'opérateur self vient en remplacement du $this. En effet, $this signifie « dans cet objet ». Comme par définition un attribut statique appartient à une classe, il serait insensé d'utiliser $this. self veut donc dire : « dans cette classe ».

I-E-1 - Méthodes statiques

1. class MaClasse {

2. // Déclaration de la méthode

3. public static function methodeStatique() {

4. echo 'méthode qui ne sert à rien';

5. }

6. }

8. // On appelle la méthode à partir de la classe

9. MaClasse::methodeStatique();

I-E-2 - Les attributs statiques

1. class MaClass {

2. // Déclaration de l'attribut statique PRIVÉ.

3. private static $_AtttributStatique = 'variable qui ne sert à rien';

5. public static function parler() {

6. // On affiche notre attribut statique

7. echo self::$_AtttributStatique;

8. }

9. }

Notez bien que contrairement à $this, avec self, l'attribut prend le « $ ». Par contre, self, lui, n'en prend pas !

L'intérêt des méthodes et attributs statiques est qu'appartenant aux classes, tout objet a accès à la même valeur, même si celle-ci vient à être modifiée par un objet ! Un petit exemple pour la route :

1. class MaClass {

2. private static $_texte = 'hello World';

4. public function changerTexte() {

5. if (self::$_texte == 'hello World') {

6. self::$_texte = 'hello Mars !';

7. }

9. else {

10. self::$_texte = 'hello World';

11. }

13. return self::$_texte;

14. }

15. }

17. $class = new maclasse();

18. echo $class->changerTexte();

20. $class2 = new maclasse();

21. echo $class2->changerTexte();

23. // ********

25. /* Affichera :

26. hello Mars !hello World

27. */

On voit donc bien que le premier objet change l'attribut statique de la classe. Ainsi, le second objet accède bien à cet attribut modifié, sinon sa méthode changerTexte aurait retourné la même valeur que celle du premier objet !

I-F - POO et BDD

Une classe ne doit répondre qu'à UNE SEULE fonction. On doit toujours garder ça à l'esprit. Donc, une instance d'une classe qui représente des données stockées en BDD a pour rôle de représenter ces données, pas de les gérer. On va pour cela créer une seconde classe, qui aura pour rôle de gérer les accès à la base. Elle prendra en général le nom de manager. Elle répondra aux fonctions CRUD.

1. class MonObjetManager {

2. // Instance de PDO

3. private $_db;

5. public function __construct($db) {

6. $this->setDb($db);

7. }

9. public function add(MonObjet $obj) {

10. $req = $this->_db->prepare('INSERT INTO nom_table (colonne1, colonne2, colonne4)

11. VALUES (:data_colonne1, :data_colonne2, :data_colonne4)');

13. $req->execute(array(

14. 'data_colonne1' => $obj->data_colonne1,

15. 'data_colonne2' => $obj->data_colonne2,

16. 'data_colonne4' => $obj->data_colonne4

17. ));

18. }

20. public function delete(MonObjet $obj) {

21. // Exécute une requête de type DELETE

22. }

24. public function get($id) {

25. // Exécute une requête de type SELECT avec une clause WHERE

26. }

28. public function getList() {

29. // Retourne la liste de toutes les entrées

30. }

32. public function update(MonObjet $obj) {

33. // Exécute une requête de type UPDATE

34. }

36. public function setDb(PDO $db) {

37. $this->_db = $db;

38. }

39. }

I-G - Hydrater ses objets

Hydrater un objet, c'est fournir des valeurs à ses attributs. Ainsi, un objet voiture est vide lorsqu'on l'instancie :

1. class Voiture {

2. private $_placesTotales;

3. private $_vitesseMax;

4. private $_nbrPortes;

6. public function getPlaces() {

7. return $this->placesTotales;

8. }

10. public function getVitesseMax {…}

12. public function getNbrPortes {…}

14. public function setPlaces($places) {

15. $places = (int) $places;

17. if ($places > 0 && < 10) {

18. this->placesTotales = $places;

19. }

20. }

22. public function setVitesseMax {…}

24. public function setNbrPortes {…}

25. }

Si l'on veut rendre cet objet opérationnel, il faut donc l'instancier et donner des valeurs à ces attributs :

$voiture = new Voiture;
voiture->setPlaces(4);
// etc

Vous conviendrez qu'il y a plus pratique… Nous allons donc mettre en place la méthode hydrate. Elle prend en général un tableau associatif en argument (nom de la propriété en clef et valeur en valeur ;)).

Première chose à toujours faire, nous avons des setters, donc on utilisera ces derniers pour attribuer des valeurs à nos propriétés. Les setters sont là pour quelque chose : ils permettent de vérifier les valeurs avant de les attribuer, on ne les court-circuitera donc pas !

Ensuite, comme nous connaissons les différentes propriétés de notre objet, nous pourrions simplement faire quelque chose comme cela :

1. class Voiture {

2. private $_placesTotales;

3. private $_vitesseMax;

4. private $_nbrPortes;

6. public function hydrate(array $donnees) {

7. $this->setPlaces($donnees['places']);

8. $this->setVitesseMax($donnees['vitesseMax']);

9. // etc

10. }

11. }

En soi, ça fonctionne parfaitement. Néanmoins, si on décide de rajouter des propriétés à notre objet, il faudra modifier notre méthode hydrate. On peut faire mieux.

1. class Voiture {

2. private $_placesTotales;

3. private $_vitesseMax;

4. private $_nbrPortes;

6. public function hydrate(array $donnees) {

7. // On fait une boucle avec le tableau de données

8. foreach ($donnees as $key => $value) {

9. // On récupère le nom des setters correspondants

10. // si la clef est placesTotales, son setter est setPlacesTotales

11. // il suffit de mettre la 1ere lettre de key en Maj et de le préfixer par set

12. $method = 'set'.ucfirst($key);

14. // On vérifie que le setter correspondant existe

15. if (method_exists($this, $method)) {

16. // S'il existe, on l'appelle

17. $this->$method($value);

18. }

19. }

20. }

22. public function getPlaces() {

23. return $this->placesTotales;

24. }

26. public function getVitesseMax {…}

28. public function getNbrPortes {…}

30. public function setPlaces($places) {

31. $places = (int) $places;

33. if ($places > 0 && $places < 10) {

34. this->placesTotales = $places;

35. }

36. }

38. public function setVitesseMax {…}

40. public function setNbrPortes {…}

41. }

Et voilà le travail, on appelle les méthodes dynamiquement. Vous remarquez d'ailleurs que method a un $ dans $this->$method($value). C'est normal puisqu'ici on appelle la variable qui représente la méthode !

I-H - L'héritage

1. class MaClasseMere {

2. // Attributs et méthodes

3. …

4. }

6. // Création d'une classe qui hérite de MaClasse

7. class MaClasseFille extends MaClasseMere {

8. // Attribut unique à la méthode fille

9. private $_monAttribut;

11. public function nouvelleMethode {

12. // Méthode qui ajoute des fonctions à la classe fille

13. }

14. }

Une classe fille hérite de toutes les méthodes et tous les attributs de la classe mère. Cependant, elle ne peut pas utiliser les attributs et méthodes en private. Elle devra donc utiliser les setters et getters correspondants.

I-H-1 - Surcharger les méthodes

1. class MaClasseMere {

2. private $_attributMere;

4. public function superMethode {

5. // Cette fonction fait des trucs

6. // …

7. }

8. }

10. class MaClasseFille extends MaClasse  {

11. // Attribut unique à la méthode fille

12. private $_monAttribut;

14. public function nouvelleMethode {

15. // Méthode qui ajoute des fonctions à la classe fille

16. }

18. public function superMethode {

19. // Méthode qui ajoute des fonctions à la classe fille

20. // on appelle d'abord la méthode de la classe mère

21. // pour ne pas écraser les instructions de celle-ci

22. parent::superMethode();

24. // Maintenant on en ajoute d'autre

25. …

26. }

27. }

Alors, petite explication ici. Surcharger une méthode, ça veut dire que la méthode de la classe fille aura plus de fonctions que celle de la classe mère. Problème, si on écrit dans la classe fille une méthode du même nom que celle héritée de la classe mère et que l'on y met des instructions, ça va faire comme si on définissait une nouvelle méthode, pas la surcharger. Pour ça, il faut donc appeler à l'intérieur de cette méthode la méthode de la classe mère avant de lui adjoindre de nouvelles instructions. Cela se fait en utilisant le mot clef parent qui, comme son nom l'indique, fait référence à la classe parente.

I-H-2 - Les contraintes

Sans contrainte, on peut utiliser les classes sans restriction et les hériter à l'infini. Il existe des contraintes pour remédier à cela.

I-H-2-a - Classe abstraite

On peut créer des classes abstraites pour empêcher de les instancier. Ces classes ne devront servir que dans le cadre d'un héritage.

abstract class ClasseAbstraite {

}

class ClasseFille extends ClasseAbstraite {

}

Cette contrainte nous garantit qu'aucune classe de type ClasseAbstraite ne sera instanciée. Dans le cas où on tenterait de le faire, une erreur fatale sera levée.

I-H-2-b - Méthode abstraite

On peut aussi déclarer une méthode comme étant abstraite. Dans ce cas, toutes les classes filles devront la réécrire. Ceci vise à forcer les classes filles à écrire une méthode donnée. Cependant, on n'inscrira aucune instruction dans la méthode de la classe mère.

1. abstract class ClasseAbstraite {

2. abstract public function methodeAbstraite();

4. // Cette méthode n'aura pas besoin d'être réécrite.

5. public function autreMethode() {

7. }

8. }

10. class ClasseFille extends ClasseAbstraite {

11. // On réécrit la méthode du même type de visibilité

12. // que la méthode abstraite « methodeAbstraite » de la classe mère.

14. public function methodeAbstraite() {

15. // Instructions.

16. …

17. }

18. }

Note : une classe doit être abstraite pour pouvoir déclarer une méthode abstraite.

I-H-2-c - Classes finales

Lorsqu'une classe est finale, il n'est pas possible d'en hériter.

1. abstract class ClasseMere {

3. }

5. // Classe finale, on ne pourra créer de classe héritant de celle-ci.

6. final class ClasseFinale extends ClasseMere {

8. }

Si on tente d'instancier une classe qui hérite d'une classe finale, on obtient une erreur fatale.

I-H-2-d - Méthodes finales

Il est aussi possible de déclarer une méthode comme étant finale. La classe fille de cette méthode pourra en hériter, mais il sera impossible de surcharger la méthode.

1. class ClasseMere {

2. // Méthode normale.

4. public function uneMethode() {

5. // Instructions.

6. }

8. // Méthode finale.

9. final public function methodeFinale() {

10. // Instructions.

11. }

12. }

14. class ClasseFille extends ClasseMere {

15. // Aucun problème.

17. public function methodeFinale() {

18. // Impossible de surcharger la méthode.

19. }

20. }

I-I - Les méthodes magiques

Ce sont des méthodes qui sont appelées lors d'un événement particulier.

__construct

À l'instanciation d'une classe.

__destruct

À la destruction d'un objet (et automatiquement à la fin de l'exécution du script).

__set

Lorsqu'on tente d'assigner une valeur à un attribut auquel on n'a pas accès (héritage en private) ou qui n'existe pas. Prend deux paramètres : le nom de l'attribut auquel on a tenté d'assigner une valeur et la valeur en question.

__get

Lorsqu'on essaye d'accéder à un attribut auquel on n'a pas accès ou qui n'existe pas. Prend comme paramètre l'attribut auquel on a tenté d'accéder.

__isset

Lorsqu'on fait un isset sur un attribut auquel on n'a pas accès ou qui n'existe pas. Prend en paramètre l'attribut sur lequel on a fait le test. Doit renvoyer un booléen (comme isset).

__unset

Lorsqu'on fait un unset sur un attribut auquel on n'a pas accès ou qui n'existe pas. Prend en paramètre l'attribut sur lequel on a fait unset. Ne doit rien renvoyer.

__call

Lorsqu'on appelle une méthode inexistante ou privée. Prend deux arguments : le nom de la méthode et un tableau avec les arguments qu'on lui a passés.

__callStatic

Lorsqu'on appelle une méthode inexistante statiquement. Prend deux arguments : le nom de la méthode et un tableau avec les arguments qu'on lui a passés. En outre, cette méthode doit obligatoirement être statique.

__toString

Lorsqu'un objet est appelé à être converti en chaîne de caractères avec un cast ou un echo.

__invoke

Lorsque l'on appelle un objet comme une fonction.

__clone

Lorsque l'on clone un objet. Cette méthode est appelée sur l'objet cloné, par sur le nouveau.

I-I-1 - Exemples

I-I-1-a - __set

1. class MaClasse {

2. private $monAttribut;

4. public function __set ($nom, $valeur) {

5. echo 'Il n\'est pas possible d\'attribuer la valeur "' .$valeur. '" à l\'attribut "' .$nom.'"';

6. }

7. }

9. $obj = new MaClasse;

10. $obj->monAttribut = 'nouvelle valeur de l\'attribut';

12. /*

13. Retournera :

14. Il n'est pas possible d'attribuer la valeur "nouvelle valeur de l'attribut" à l'attribut "monAttribut"

15. */

16. ?>

I-I-1-b - __invoke

1. class MaClasse {

2. public function __invoke ($argument) {

3. echo $argument;

4. }

5. }

7. $obj = new MaClasse;

8. $obj (5); // Affiche « 5 ».

I-J - Clonage

Il faut savoir qu'au niveau des objets, la variable représentant l'objet ne contient pas l'objet lui-même, mais un id pointant vers celui-ci. Donc si on fait $objet1 = $objet2 et que l'on modifie l'un des deux, les deux seront modifiés. C'est comme si l'on avait créé un alias, c'est d'ailleurs pour cette raison qu'il y a une fonction de clonage.

$objet = new maClasse;
$objet2 = clone $objet;

I-K - Comparaisons

Pour comparer deux objets :

==

Vérifie que les deux objets sont issus d'une même classe. Ainsi, même s'ils sont identiques (méthodes et attributs), mais issus de deux classes différentes, == renverra FALSE;

===

Vérifie que les deux objets correspondent à la même instance. Deux clones renverront donc FALSE.

I-L - Interfaces

Une interface est une classe complètement abstraite. Elles sont différentes de l'héritage, car elles ne représentent pas un sous-ensemble, elles décrivent un comportement à un objet. Ainsi, il est logique que des classes voiture et moto héritent d'une classe véhicule, mais pas la classe son. En revanche, toutes ces classes peuvent implémenter l'interface vitesse, car voiture, comme moto ou son, ont une vitesse de déplacement.

On implémente une interface comme ceci :

1. interface vitesse {

2. public function rapidite($kmh);

3. }

5. class voiture implements vitesse {

6. public function rapidite($kmh) {

8. }

9. }

- Toute méthode contenue dans une interface doit être publique.

- Les méthodes des interfaces ne doivent rien contenir, elles servent simplement à obliger les classes qui les implémentent à créer ces méthodes.

- Une interface ne peut pas lister de méthode abstraite ou finale.

- On ne peut donner à une interface le même nom qu'une classe.

Il est possible d'implémenter plusieurs interfaces dans une même classe, on séparera leurs noms par des virgules.

Il est aussi possible d'hériter des interfaces entre elles, et contrairement aux classes (mais de manière similaire à implements) il est possible d'hériter de plusieurs interfaces à la fois. Pour ce faire, comme pour l'implémentation, il suffit de séparer le nom des interfaces par une virgule.

De nombreuses interfaces prédéfinies existent dans la SPL. Je vous laisse vous référer à la doc pour ça.

I-M - Exceptions

Les exceptions sont une manière différente de gérer les erreurs. Au lieu d'avoir les erreurs standard de PHP (erreurs fatales, alertes, notices ou parse errors), on peut obtenir des erreurs personnalisées et les « attraper », ce qui permettra la poursuite du script.

On peut lancer une exception depuis n'importe où dans notre code. Pour cela, il faut créer une instance de la classe Exception. La classe prend trois arguments (tous sont facultatifs) :

1. Le message d'erreur ;

2. Le code d'erreur ;

3. L'exception précédente.

1. // on crée une fonction qui calcule l'aire d'un rectangle

2. function aire($largeur, $longueur) {

3. if (!is_numeric($largeur) OR !is_numeric($longueur)) {

4. throw new Exception('Les paramètres doivent être des nombres');

5. }

7. else {

8. echo $largeur * $longueur;

9. }

10. }

12. try {

13. aire(7, 45);

14. }

16. catch(Exception $e) {

17. echo $e->getMessage();

18. }

Il est possible d'hériter la classe exception pour la personnaliser. Pour cela, se reporter à la doc. De plus, la bibliothèque SPL contient de nombreuses exceptions standard.

Enfin, sachez qu'il est aussi possible d'avoir plusieurs blocs catch dans le code. Cela permet par exemple, en utilisant des exceptions adaptées, de mieux se repérer dans le code et d'effectuer la capture d'une expression donnée à un endroit précis. Cependant, si on précise exception dans le bloc catch, ceci attrapera toutes les exceptions, car elles héritent forcément toutes de celle-ci.

I-N - Les classes anonymes

Actuellement, on sait que pour créer une classe, il faut la déclarer dans un fichier, définir son namespace, inclure le fichier et utiliser la classe avec son namespace. Ce n'est pas très rapide et ça prend de la place, surtout si l'on ne va l'utiliser qu'une fois. PHP7 permet de créer des classes anonymes, des classes n'ayant pas de nom et qui sont instanciables à l'endroit de l'utilisation.

1. // Exemple venant de https://secure.php.net/manual/fr/language.oop5.anonymous.php

2. $util->setLogger(new class {

3. public function log($msg)

4. {

5. echo $msg;

6. }

7. });

I-O - Réflexion

C'est un point intéressant de la POO. PHP fourni une API de réflexion qui permet de faire du reverse-engineering sur les classes, les méthodes, les fonctions et les extensions. Pour en apprendre davantage là-dessus, rien ne vaut, une fois de plus, un tour dans la doc officielle.

Un grand merci à Benjamin Rothan pour la relecture et l'ajout du paragraphe sur les classes anonymes !

II - Note de la rédaction de Developpez.com

Nous tenons à remercier Quentin Busuttil qui nous a aimablement autorisés à publier son tutoriel : Synthèse de la POO en PHP. Nous remercions également ced pour la relecture orthographique.