Apprentissage avancé de la Programmation Backend avec Laravel et PHP
Apprentissage avancé de la Programmation Backend avec Laravel et PHP

Programmation orientée objet (POO) en PHP : classes, héritage, interfaces

Introduction à la Programmation Orientée Objet (POO)

Bienvenue dans ce cours sur la Programmation Orientée Objet (POO) en PHP ! Si vous êtes ici, c'est que vous progressez dans votre maîtrise du PHP et que vous êtes prêt à aborder des concepts fondamentaux pour le développement backend moderne, en particulier avec un framework comme Laravel.

La POO est un paradigme de programmation qui s'organise autour des "objets" plutôt que des "actions" ou de la logique. En d'autres termes, plutôt que de se concentrer sur des fonctions séquentielles, la POO se concentre sur les entités (objets) qui combinent à la fois des données (leurs propriétés) et des comportements (leurs méthodes).

Pourquoi la POO est-elle cruciale pour le développement avec Laravel et PHP ?

  • Modularité : Le code est organisé en blocs autonomes (objets), ce qui facilite sa gestion et sa compréhension.
  • Réutilisabilité : Les objets peuvent être réutilisés dans différentes parties d'une application ou même dans d'autres projets, réduisant ainsi le code redondant.
  • Maintenabilité : Les changements dans une partie du code ont moins de chances d'affecter d'autres parties, simplifiant la maintenance et l'évolution.
  • Évolutivité : Il est plus facile d'ajouter de nouvelles fonctionnalités sans casser celles existantes.
  • Collaboration : La structure claire de la POO facilite le travail d'équipe sur de grands projets.
  • Laravel : Le framework Laravel est entièrement construit sur les principes de la POO. Comprendre la POO est donc indispensable pour maîtriser Laravel.

Dans cette leçon, nous allons explorer les piliers essentiels de la POO en PHP : les classes et les objets, l'héritage, et les interfaces.

1. Classes et Objets : Les Fondations de la POO

Au cœur de la POO se trouvent les concepts de classe et d'objet. C'est la première étape pour penser "orienté objet".

1.1. Qu'est-ce qu'une Classe ?

Une classe est un schéma, un plan, ou un modèle pour créer des objets. Elle ne représente pas une entité concrète, mais plutôt la définition de ce que seront les objets de ce type.

Imaginez la classe comme le plan d'une voiture : elle définit qu'une voiture a des roues, un moteur, une couleur, et qu'elle peut démarrer, freiner, etc. Ce plan ne roule pas, mais il permet de construire de nombreuses voitures réelles.

Une classe définit :

  • Des propriétés (ou attributs) : Ce sont les variables qui décrivent l'état d'un objet (ex: couleur, marque, vitesse).
  • Des méthodes : Ce sont les fonctions qui définissent le comportement de l'objet (ex: démarrer, freiner, accélérer).

1.2. Qu'est-ce qu'un Objet ?

Un objet est une instance concrète d'une classe. C'est la réalisation du plan. Si la classe est le plan d'une voiture, un objet est une voiture spécifique que vous pouvez conduire (par exemple, une "Renault Clio rouge" ou une "Peugeot 308 bleue").

Chaque objet a ses propres valeurs pour les propriétés définies par sa classe.

1.3. Déclaration d'une Classe et Visibilité

En PHP, une classe est déclarée en utilisant le mot-clé class.

<?php

// Déclaration de la classe Voiture
class Voiture
{
    // Propriétés (attributs) de la classe
    public string $marque;
    public string $modele;
    public string $couleur;
    private int $vitesseActuelle = 0; // Vitesse initiale à 0

    // Méthode constructeur : appelée automatiquement lors de la création d'un objet
    public function __construct(string $marque, string $modele, string $couleur)
    {
        $this->marque = $marque;
        $this->modele = $modele;
        $this->couleur = $couleur;
        // On n'initialise pas vitesseActuelle ici car elle a déjà une valeur par défaut.
        // On pourrait la passer en argument si on voulait.
    }

    // Méthodes (comportements) de la classe

    /**
     * Accélère la voiture d'une certaine quantité.
     * @param int $quantite L'augmentation de vitesse.
     */
    public function accelerer(int $quantite): void
    {
        $this->vitesseActuelle += $quantite;
        echo "La " . $this->modele . " accélère. Vitesse actuelle : " . $this->vitesseActuelle . " km/h." . PHP_EOL;
    }

    /**
     * Freine la voiture d'une certaine quantité.
     * @param int $quantite La diminution de vitesse.
     */
    public function freiner(int $quantite): void
    {
        $this->vitesseActuelle -= $quantite;
        if ($this->vitesseActuelle < 0) {
            $this->vitesseActuelle = 0;
        }
        echo "La " . $this->modele . " freine. Vitesse actuelle : " . $this->vitesseActuelle . " km/h." . PHP_EOL;
    }

    /**
     * Retourne la vitesse actuelle de la voiture.
     * @return int La vitesse actuelle.
     */
    public function getVitesseActuelle(): int
    {
        return $this->vitesseActuelle;
    }

    /**
     * Affiche les informations de la voiture.
     */
    public function afficherInfos(): void
    {
        echo "Ceci est une voiture : " . $this->couleur . " " . $this->marque . " " . $this->modele . "." . PHP_EOL;
        echo "Vitesse actuelle : " . $this->getVitesseActuelle() . " km/h." . PHP_EOL;
    }
}

Explications du code ci-dessus :

  • class Voiture : Déclare une nouvelle classe nommée Voiture.
  • Propriétés ($marque, $modele, $couleur, $vitesseActuelle) :
    • Elles stockent les données propres à chaque voiture.
    • public : La propriété ou méthode est accessible de partout (depuis l'intérieur de la classe, depuis les classes héritées, et depuis l'extérieur de l'objet).
    • private : La propriété ou méthode n'est accessible que de l'intérieur de la classe où elle est définie. C'est une bonne pratique pour encapsuler l'état interne de l'objet, comme $vitesseActuelle.
    • protected : (Nous le verrons avec l'héritage) La propriété ou méthode est accessible depuis la classe elle-même et depuis les classes qui en héritent.
    • Type Hinting (string, int) : Depuis PHP 7.4 (et amélioré en PHP 8), on peut spécifier le type de données que la propriété doit contenir. C'est une excellente pratique pour la clarté et la détection d'erreurs.
  • Constructeur (__construct) :
    • C'est une méthode spéciale qui est automatiquement appelée chaque fois qu'un nouvel objet de cette classe est créé.
    • Elle est utilisée pour initialiser l'état de l'objet (par exemple, donner une marque et un modèle à une nouvelle voiture).
    • $this-> : Fait référence à l'instance actuelle de l'objet. Il est utilisé pour accéder aux propriétés et méthodes de l'objet.
  • Méthodes (accelerer, freiner, getVitesseActuelle, afficherInfos) :
    • Définissent les actions que l'objet peut effectuer.
    • Elles ont aussi une visibilité (public).
    • void : Indique que la méthode ne retourne aucune valeur.
    • int : Indique que la méthode retourne un entier.

1.4. Création et Utilisation d'Objets

Pour créer un objet (une instance) d'une classe, on utilise le mot-clé new.

<?php

// ... (code de la classe Voiture ci-dessus)

// Création d'objets (instances de la classe Voiture)
$maVoiture = new Voiture("Renault", "Clio", "Bleue");
$saVoiture = new Voiture("Peugeot", "308", "Noire");

echo "--- Utilisation de \$maVoiture ---" . PHP_EOL;
$maVoiture->afficherInfos(); // Affiche les infos initiales
$maVoiture->accelerer(50);   // La voiture accélère
$maVoiture->accelerer(20);
$maVoiture->freiner(30);     // La voiture freine
echo "Vitesse actuelle de ma Clio : " . $maVoiture->getVitesseActuelle() . " km/h." . PHP_EOL;

echo PHP_EOL . "--- Utilisation de \$saVoiture ---" . PHP_EOL;
$saVoiture->afficherInfos();
$saVoiture->accelerer(100);
$saVoiture->freiner(50);
echo "Vitesse actuelle de sa 308 : " . $saVoiture->getVitesseActuelle() . " km/h." . PHP_EOL;

/*
// Exemple d'accès direct à une propriété publique et tentative d'accès à une propriété privée
echo "Marque de ma voiture : " . $maVoiture->marque . PHP_EOL; // Accès autorisé (public)
// echo "Vitesse privée : " . $maVoiture->vitesseActuelle . PHP_EOL; // ERREUR FATALE : propriété privée
*/

Résultat attendu de l'exécution :

--- Utilisation de $maVoiture ---
Ceci est une voiture : Bleue Renault Clio.
Vitesse actuelle : 0 km/h.
La Clio accélère. Vitesse actuelle : 50 km/h.
La Clio accélère. Vitesse actuelle : 70 km/h.
La Clio freine. Vitesse actuelle : 40 km/h.
Vitesse actuelle de ma Clio : 40 km/h.

--- Utilisation de $saVoiture ---
Ceci est une voiture : Noire Peugeot 308.
Vitesse actuelle : 0 km/h.
La 308 accélère. Vitesse actuelle : 100 km/h.
La 308 freine. Vitesse actuelle : 50 km/h.
Vitesse actuelle de sa 308 : 50 km/h.

Chaque variable ($maVoiture, $saVoiture) est une instance indépendante de la classe Voiture, avec son propre état ($vitesseActuelle). C'est l'essence de la POO : gérer des entités autonomes et encapsulées. L'encapsulation est le principe qui consiste à cacher l'état interne d'un objet et à n'exposer que les méthodes nécessaires pour interagir avec lui. C'est pourquoi $vitesseActuelle est private et accessible uniquement via getVitesseActuelle().

2. L'Héritage : Réutiliser et Étendre le Code

L'héritage est un mécanisme puissant de la POO qui permet à une classe d'hériter des propriétés et des méthodes d'une autre classe. Cela modélise une relation "est-un(e)" (is-a) entre les classes.

  • La classe qui hérite est appelée classe enfant (ou sous-classe, classe dérivée).
  • La classe dont on hérite est appelée classe parente (ou super-classe, classe de base).

Avantages de l'héritage :

  • Réutilisation du code : Évite de réécrire le même code dans plusieurs classes.
  • Extension : Permet d'ajouter de nouvelles fonctionnalités ou de modifier le comportement existant des classes parentes sans les altérer.
  • Modélisation hiérarchique : Facilite la représentation de relations du monde réel (ex: Une VoitureElectrique est une Voiture).

2.1. Syntaxe de l'Héritage

On utilise le mot-clé extends pour indiquer l'héritage.

<?php

// ... (classe Voiture définie précédemment)

// Déclaration de la classe VoitureElectrique qui hérite de Voiture
class VoitureElectrique extends Voiture
{
    private int $autonomieBatterie; // Nouvelle propriété spécifique aux voitures électriques

    // Constructeur de la classe enfant
    public function __construct(string $marque, string $modele, string $couleur, int $autonomieBatterie)
    {
        // Appelle le constructeur de la classe parente (Voiture)
        // C'est CRUCIAL pour initialiser les propriétés héritées.
        parent::__construct($marque, $modele, $couleur);
        $this->autonomieBatterie = $autonomieBatterie;
    }

    // Méthode spécifique à VoitureElectrique
    public function recharger(): void
    {
        echo "La voiture électrique " . $this->modele . " est en charge." . PHP_EOL;
        $this->autonomieBatterie = 100; // Simule une recharge complète
    }

    // Surcharge de méthode (Method Overriding) : redéfinition d'une méthode parente
    public function afficherInfos(): void
    {
        // Appelle la méthode afficherInfos() de la classe parente pour réutiliser le code
        parent::afficherInfos();
        echo "Type : Électrique." . PHP_EOL;
        echo "Autonomie de batterie restante : " . $this->autonomieBatterie . "%." . PHP_EOL;
    }

    // Tentative de surcharge d'une méthode 'final' si elle existait (ça provoquerait une erreur)
    // public final function accelerer(): void { ... } // ERREUR si parent::accelerer était final
}

2.2. Concepts Clés de l'Héritage

  • parent:: : Permet d'appeler une méthode ou le constructeur de la classe parente. C'est essentiel pour ne pas dupliquer la logique d'initialisation ou de comportement de base.
  • Surcharge de Méthodes (Method Overriding) : Une classe enfant peut redéfinir une méthode qui existe déjà dans sa classe parente. Lorsqu'un objet de la classe enfant appelle cette méthode, c'est la version de la classe enfant qui est exécutée.
  • protected Visibilité : Les propriétés ou méthodes protected d'une classe parente sont accessibles dans les classes enfants, mais pas depuis l'extérieur de la hiérarchie. C'est un bon compromis pour donner de la flexibilité aux classes filles tout en maintenant une certaine encapsulation.
  • Mot-clé final :
    • final class MaClasse { ... } : Empêche toute autre classe d'hériter de MaClasse.
    • public final function maMethode() { ... } : Empêche les classes enfants de surcharger cette méthode. Utile pour garantir qu'une logique critique n'est pas modifiée.

2.3. Utilisation de l'Héritage

<?php

// ... (classes Voiture et VoitureElectrique définies précédemment)

echo "--- Création et utilisation d'une VoitureElectrique ---" . PHP_EOL;
$maVoitureElectrique = new VoitureElectrique("Tesla", "Model 3", "Blanche", 90);

$maVoitureElectrique->afficherInfos(); // Utilise la méthode surchargée
$maVoitureElectrique->accelerer(60);   // Utilise la méthode héritée de Voiture
$maVoitureElectrique->recharger();      // Utilise la méthode spécifique à VoitureElectrique
$maVoitureElectrique->afficherInfos();

Résultat attendu de l'exécution :

--- Création et utilisation d'une VoitureElectrique ---
Ceci est une voiture : Blanche Tesla Model 3.
Vitesse actuelle : 0 km/h.
Type : Électrique.
Autonomie de batterie restante : 90%.
La Model 3 accélère. Vitesse actuelle : 60 km/h.
La voiture électrique Model 3 est en charge.
Ceci est une voiture : Blanche Tesla Model 3.
Vitesse actuelle : 60 km/h.
Type : Électrique.
Autonomie de batterie restante : 100%.

Comme on peut le voir, la VoitureElectrique a toutes les fonctionnalités d'une Voiture (comme accelerer), mais elle a aussi des fonctionnalités et des informations supplémentaires (comme recharger et $autonomieBatterie), et elle peut présenter ses informations de manière spécifique grâce à la surcharge de afficherInfos().

3. Les Interfaces : Définir des Contrats

Les interfaces sont un concept fondamental pour construire des applications robustes et flexibles en POO, particulièrement avec des frameworks comme Laravel qui utilisent massivement ce concept pour la conception basée sur les contrats.

Une interface est comme un contrat. Elle définit un ensemble de méthodes que les classes doivent implémenter. Elle ne contient aucune implémentation de code, seulement la signature des méthodes (leur nom, leurs arguments, leur type de retour).

3.1. Qu'est-ce qu'une Interface ?

  • Une interface est une définition de comportement.
  • Elle force les classes qui l'implémentent à fournir une implémentation pour toutes les méthodes qu'elle déclare.
  • C'est un outil pour réaliser le polymorphisme (la capacité pour des objets de types différents à répondre au même appel de méthode).
  • Toutes les méthodes déclarées dans une interface sont implicitement public.

3.2. Syntaxe des Interfaces

On utilise le mot-clé interface pour déclarer une interface et implements pour qu'une classe implémente cette interface.

<?php

// Déclaration de l'interface VehiculeInterface
interface VehiculeInterface
{
    public function accelerer(int $quantite): void;
    public function freiner(int $quantite): void;
    public function getVitesseActuelle(): int;
    public function afficherInfos(): void;
}

// La classe Voiture implémente l'interface VehiculeInterface
// Elle DOIT implémenter toutes les méthodes définies dans l'interface
class Voiture implements VehiculeInterface
{
    // ... (propriétés et constructeur comme avant) ...
    public string $marque;
    public string $modele;
    public string $couleur;
    private int $vitesseActuelle = 0;

    public function __construct(string $marque, string $modele, string $couleur)
    {
        $this->marque = $marque;
        $this->modele = $modele;
        $this->couleur = $couleur;
    }

    // Implémentation des méthodes de l'interface
    public function accelerer(int $quantite): void
    {
        $this->vitesseActuelle += $quantite;
        echo "La " . $this->modele . " accélère. Vitesse actuelle : " . $this->vitesseActuelle . " km/h." . PHP_EOL;
    }

    public function freiner(int $quantite): void
    {
        $this->vitesseActuelle -= $quantite;
        if ($this->vitesseActuelle < 0) {
            $this->vitesseActuelle = 0;
        }
        echo "La " . $this->modele . " freine. Vitesse actuelle : " . $this->vitesseActuelle . " km/h." . PHP_EOL;
    }

    public function getVitesseActuelle(): int
    {
        return $this->vitesseActuelle;
    }

    public function afficherInfos(): void
    {
        echo "Ceci est une voiture : " . $this->couleur . " " . $this->marque . " " . $this->modele . "." . PHP_EOL;
        echo "Vitesse actuelle : " . $this->getVitesseActuelle() . " km/h." . PHP_EOL;
    }
}

// La classe VoitureElectrique hérite de Voiture et donc indirectement implémente VehiculeInterface
class VoitureElectrique extends Voiture implements VehiculeInterface // On peut spécifier l'interface même si déjà héritée
{
    private int $autonomieBatterie;

    public function __construct(string $marque, string $modele, string $couleur, int $autonomieBatterie)
    {
        parent::__construct($marque, $modele, $couleur);
        $this->autonomieBatterie = $autonomieBatterie;
    }

    public function recharger(): void
    {
        echo "La voiture électrique " . $this->modele . " est en charge." . PHP_EOL;
        $this->autonomieBatterie = 100;
    }

    // Surcharge de méthode, mais elle respecte toujours le contrat de l'interface
    public function afficherInfos(): void
    {
        parent::afficherInfos();
        echo "Type : Électrique." . PHP_EOL;
        echo "Autonomie de batterie restante : " . $this->autonomieBatterie . "%." . PHP_EOL;
    }
}

// Une autre classe qui implémente la même interface, mais avec une logique différente
class Velo implements VehiculeInterface
{
    private string $type;
    private int $vitesseActuelle = 0;

    public function __construct(string $type)
    {
        $this->type = $type;
    }

    public function accelerer(int $quantite): void
    {
        $this->vitesseActuelle += $quantite / 2; // Un vélo accélère moins vite :)
        echo "Le vélo de type " . $this->type . " accélère. Vitesse actuelle : " . $this->vitesseActuelle . " km/h." . PHP_EOL;
    }

    public function freiner(int $quantite): void
    {
        $this->vitesseActuelle -= $quantite / 2;
        if ($this->vitesseActuelle < 0) {
            $this->vitesseActuelle = 0;
        }
        echo "Le vélo de type " . $this->type . " freine. Vitesse actuelle : " . $this->vitesseActuelle . " km/h." . PHP_EOL;
    }

    public function getVitesseActuelle(): int
    {
        return $this->vitesseActuelle;
    }

    public function afficherInfos(): void
    {
        echo "Ceci est un vélo de type : " . $this->type . "." . PHP_EOL;
        echo "Vitesse actuelle : " . $this->getVitesseActuelle() . " km/h." . PHP_EOL;
    }
}

3.3. Polymorphisme et Avantages des Interfaces

Le vrai pouvoir des interfaces réside dans le polymorphisme. Cela signifie que vous pouvez traiter des objets de différentes classes de manière uniforme, tant qu'ils implémentent la même interface.

<?php

// ... (classes Voiture, VoitureElectrique, Velo et VehiculeInterface définies précédemment)

// Fonction qui peut prendre n'importe quel objet qui implémente VehiculeInterface
function faireRoulerVehicule(VehiculeInterface $vehicule): void
{
    echo PHP_EOL . "--- Traitement d'un véhicule ---" . PHP_EOL;
    $vehicule->afficherInfos();
    $vehicule->accelerer(30);
    $vehicule->freiner(10);
    $vehicule->afficherInfos();
}

$maVoiture = new Voiture("Citroen", "C4", "Grise");
$maVoitureElectrique = new VoitureElectrique("Nissan", "Leaf", "Verte", 75);
$monVelo = new Velo("VTT");

faireRoulerVehicule($maVoiture);
faireRoulerVehicule($maVoitureElectrique);
faireRoulerVehicule($monVelo);

Résultat attendu de l'exécution :

--- Traitement d'un véhicule ---
Ceci est une voiture : Grise Citroen C4.
Vitesse actuelle : 0 km/h.
La C4 accélère. Vitesse actuelle : 30 km/h.
La C4 freine. Vitesse actuelle : 20 km/h.
Ceci est une voiture : Grise Citroen C4.
Vitesse actuelle : 20 km/h.

--- Traitement d'un véhicule ---
Ceci est une voiture : Verte Nissan Leaf.
Vitesse actuelle : 0 km/h.
Type : Électrique.
Autonomie de batterie restante : 75%.
La Leaf accélère. Vitesse actuelle : 30 km/h.
La Leaf freine. Vitesse actuelle : 20 km/h.
Ceci est une voiture : Verte Nissan Leaf.
Vitesse actuelle : 20 km/h.
Type : Électrique.
Autonomie de batterie restante : 75%.

--- Traitement d'un véhicule ---
Ceci est un vélo de type : VTT.
Vitesse actuelle : 0 km/h.
Le vélo de type VTT accélère. Vitesse actuelle : 15 km/h.
Le vélo de type VTT freine. Vitesse actuelle : 10 km/h.
Ceci est un vélo de type : VTT.
Vitesse actuelle : 10 km/h.

Avantages clés des interfaces en programmation backend (et Laravel) :

  • Découplage (Loose Coupling) : La fonction faireRoulerVehicule ne dépend pas d'une classe spécifique (Voiture ou Velo), mais d'un contrat (VehiculeInterface). Si vous créez une nouvelle classe Moto qui implémente VehiculeInterface, faireRoulerVehicule fonctionnera sans modification. Cela rend le code plus flexible et moins sujet aux changements en cascade.
  • Testabilité : Vous pouvez facilement créer des "mocks" ou des "stubs" d'une interface pour vos tests unitaires, sans avoir à instancier des classes complexes réelles.
  • Architectures modulaires : Laravel utilise des interfaces pour définir ses "Contracts" (Contrats), qui sont des interfaces définissant les services offerts par le framework. Cela permet de remplacer facilement une implémentation par une autre (ex: changer de système de cache, de base de données, etc.) sans modifier le code qui utilise ces services.
  • Standardisation : Garantit que différentes classes offrant des fonctionnalités similaires le font de manière cohérente, en respectant un certain "contrat".

Conclusion

Félicitations ! Vous avez parcouru les concepts fondamentaux de la Programmation Orientée Objet en PHP :

  • Les Classes sont des modèles qui définissent les propriétés (données) et les méthodes (comportements) d'un type d'entité.
  • Les Objets sont des instances concrètes de ces classes, possédant leur propre état et capacité à agir. L'encapsulation gère leur visibilité.
  • L'Héritage permet à une classe enfant de réutiliser et d'étendre les fonctionnalités d'une classe parente, établissant une relation "est-un(e)".
  • Les Interfaces définissent des contrats de comportement que les classes s'engagent à respecter, promouvant le polymorphisme et le découplage dans votre architecture.

La maîtrise de ces concepts est indispensable pour tout développeur PHP souhaitant créer des applications robustes, maintenables et évolutives, et plus particulièrement pour plonger dans le monde de Laravel. Le framework est une application POO par excellence, et comprendre ces principes vous ouvrira les portes de sa puissance et de sa flexibilité.

Continuez à pratiquer, à expérimenter avec ces concepts, et vous verrez comment ils transforment votre approche du développement !