Laisser un commentaire

Synthèse de la POO en PHP

Ce post é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 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 !

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.

class MaClasse {
    private $_monAttribut = 'je ne sers à rien';

    public function changerAttribut() {
        $this->_monAttribut .= ' et je ne sers toujours à rien !';
        return $this->_monAttribut;
    }
}

$obj = new MaClasse;
echo $obj->changerAttribut();

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

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.

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 de d’inclure automatiquement mes classes.

function chargerClasse ($classe) {
    // On inclue la classe correspondante au paramètre passé
    require $classe . '.class.php';
}

Ok jusque là ça ne sert pas à grand chose… Mais c’est sans compter sur spl_autoload_register. Cette fonction de php permet d’enregister 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 !

// On enregistre la fonction en autoload
// pour qu'elle soit appelée dès qu'on instanciera une classe non déclarée
spl_autoload_register ('chargerClasse');

$obj = new MaClasseNonDéclarée;

Constantes de classe

class MaClasse {
    // Déclaration de la constante
    const MA_CONSTANTE = 20;
}

// Instanciation de l'objet
$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 ->.

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”.

Méthodes statiques

class MaClasse {
    // Déclaration de la méthode
    public static function methodeStatique() {
        echo 'méthode qui ne sert à rien';
    }
}

// On appelle la méthode à partir de la classe
MaClasse::methodeStatique();

Les attributs statiques

class MaClass {
    // Déclaration de l'attribut statique PRIVÉE.
    private static $_AtttributStatique = 'variable qui ne sert à rien';

    public static function parler() {
        // On affiche notre attribut statique
        echo self::$_AtttributStatique;
    }
}

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 :

class MaClass {
    private static $_texte = 'hello World';

    public function changerTexte() {
        if (self::$_texte == 'hello World') {
            self::$_texte = 'hello Mars !';
        }

        else {
            self::$_texte = 'hello World';
        }

        return self::$_texte;
    }
}

$class = new maclasse();
echo $class->changerTexte();

$class2 = new maclasse();
echo $class2->changerTexte();

// ********

/* Affichera :
  hello Mars !hello World
*/

On voit donc bien que le 1er 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 !

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.

class MonObjetManager {
    // Instance de PDO
    private $_db;

    public function __construct($db) {
        $this->setDb($db);
    }

    public function add(MonObjet $obj) {
        $req = $this->_db->prepare('INSERT INTO nom_table (colonne1, colonne2, colonne4)
                                   VALUES (:data_colonne1, :data_colonne2, :data_colonne4)');

        $req->execute(array(
            'data_colonne1' => $obj->data_colonne1,
            'data_colonne2' => $obj->data_colonne2,
            'data_colonne4' => $obj->data_colonne4
        ));
    }

    public function delete(MonObjet $obj) {
        // Exécute une requête de type DELETE
    }

    public function get($id) {
        // Exécute une requête de type SELECT avec une clause WHERE
    }

    public function getList() {
        // Retourne la liste de toutes les entrées
    }

    public function update(MonObjet $obj) {
        // Exécute une requête de type UPDATE
    }

    public function setDb(PDO $db) {
        $this->_db = $db;
    }
}

Hydrater ses objets

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

class Voiture {
    private $_placesTotales;
    private $_vitesseMax;
    private $_nbrPortes;

    public function getPlaces() {
        return $this->placesTotales;
    }

    public function getVitesseMax {…}

    public function getNbrPortes {…}

    public function setPlaces($places) {
        $places = (int) $places;

        if ($places > 0 && < 10) {
            this->placesTotales = $places;
        }
    }

    public function setVitesseMax {…}

    public function setNbrPortes {…}
}

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 :

class Voiture {
    private $_placesTotales;
    private $_vitesseMax;
    private $_nbrPortes;

    public function hydrate(array $donnees) {
        $this->setPlaces($donnees['places']);
        $this->setVitesseMax($donnees['vitesseMax']);
        // etc
    }
}

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

class Voiture {
    private $_placesTotales;
    private $_vitesseMax;
    private $_nbrPortes;

    public function hydrate(array $donnees) {
        // On fait une boucle avec le tableau de données
        foreach ($donnees as $key => $value) {
            // On récupère le nom des setters correspondants
            // si la clef est placesTotales son setter est setPlacesTotales
            // il suffit de mettre la 1ere lettre de key en Maj et de le préfixer par set
            $method = 'set'.ucfirst($key);

            // On vérifie que le setter correspondant existe
            if (method_exists($this, $method)) {
                // S'il existe, on l'appelle
                $this->$method($value);
            }
        }
    }

    public function getPlaces() {
        return $this->placesTotales;
    }

    public function getVitesseMax {…}

    public function getNbrPortes {…}

    public function setPlaces($places) {
        $places = (int) $places;

        if ($places > 0 && $places < 10) {
            this->placesTotales = $places;
        }
    }

    public function setVitesseMax {…}

    public function setNbrPortes {…}
}

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 !

L’héritage

class MaClasseMere {
    // Attributs et méhodes
     …
}

// Création d'une classe qui hérite de MaClasse
class MaClasseFille extends MaClasseMere {
    // Attribut unique à la méthode fille
    private $_monAttribut;

    public function nouvelleMethode {
        // Méthode qui ajoute des fonctions à la classe fille
    }
}

Une classe fille hérite de toutes les méthodes et 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.

Surcharger les méthodes

class MaClasseMere {
    private $_attributMere;

    public function superMethode {
        // Cette fonction fait des trucs
        // …
    }
}

class MaClasseFille extends MaClasse  {
    // Attribut unique à la méthode fille
    private $_monAttribut;

    public function nouvelleMethode {
        // Méthode qui ajoute des fonctions à la classe fille
    }

    public function superMethode {
        // Méthode qui ajoute des fonctions à la classe fille
        // on appelle d'abord la méthode de la classe mère
        // pour ne pas écraser les instructions de celle-ci
        parent::superMethode();

        // Maintenant on en ajoute d'autre
        …
    }
}

Alors, petite explication ici. Surcharger une méthode, ça veut dire que la méthode de la classe fille aura plus de fonction 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é 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 ce fait en utilisant le mot clef parent qui, comme son nom l’indique, fait référence à la classe parente.

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.

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 garantie qu’aucune classe de type ClasseAbstraite ne sera instancié. Dans le cas où on tenterait de le faire, une erreur fatale sera levée.

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.

abstract class ClasseAbstraite {
    abstract public function methodeAbstraite();

    // Cette méthode n'aura pas besoin d'être réécrite.
    public function autreMethode() {

    }
}

class ClasseFille extends ClasseAbstraite {
    // On réécrit la méthode du même type de visibilité
    // que la méthode abstraite « methodeAbstraite » de la classe mère.

    public function methodeAbstraite() {
        // Instructions.
        …
    }
}

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

Classes finales

Lorsqu’une classe est finale, il n’est pas possible de l’hériter.

abstract class ClasseMere {

}

// Classe finale, on ne pourra créer de classe héritant de celle-ci.    
final class ClasseFinale extends ClasseMere {

}

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

Méthode 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.

class ClasseMere {
    // Méthode normale.

    public function uneMethode() {
        // Instructions.
    }

    // Méthode finale.
    final public function methodeFinale() {
        // Instructions.
    }
}

class ClasseFille extends ClasseMere {
    // Aucun problème.

    public function methodeFinale() {
        // Impossible de surcharger la méthode.
    }
}

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 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é sur l'objet cloné, par sur le nouveau.

Exemples

__set

class MaClasse {
    private $monAttribut;

    public function __set ($nom, $valeur) {
        echo 'Il n\'est pas possible d\'attribuer la valeur "' .$valeur. '" à l\'attribut "' .$nom.'"';
    }
}

$obj = new MaClasse;
$obj->monAttribut = 'nouvelle valeur de l\'attribut';

/*
  Retournera :
  Il n'est pas possible d'attribuer la valeur "nouvelle valeur de l'attribut" à l'attribut "monAttribut"
*/
?>

__invoke

class MaClasse {
    public function __invoke ($argument) {
        echo $argument;
    }
}

$obj = new MaClasse;
$obj (5); // Affiche « 5 ».

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;

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.

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 :

interface vitesse {
    public function rapidite($kmh);
}

class voiture implements vitesse {
    public function rapidite($kmh) {

    }
}

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.

Exceptions

Les exceptions sont une manière différente de gérer les erreurs. Au lieu d’avoir les erreurs standards 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 celà, 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.
// on créé une fonction qui calcule l'aire d'un rectangle
function aire($largeur, $longueur) {
    if (!is_numeric($largeur) OR !is_numeric($longueur)) {
        throw new Exception('Les paramètres doivent être des nombres');
    }

    else {
        echo $largeur * $longueur;
    }
}

try {
    aire(7, 45);
}

catch(Exception $e) {
    echo $e->getMessage();
}

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 standards.

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.

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 prends 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.

// Exemple venant de https://secure.php.net/manual/fr/language.oop5.anonymous.php
$util->setLogger(new class {
    public function log($msg) {
        echo $msg;
    }
});

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 d’avantage 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 !

Commentaires

Rejoignez la discussion !

Vous pouvez utiliser Markdown pour les liens [ancre de lien](url), la mise en *italique* et en **gras**. Enfin pour le code, vous pouvez utiliser la syntaxe `inline` et la syntaxe bloc

```
ceci est un bloc
de code
```