POO - Les Classes en PHP

Découvrez les fondamentaux de la Programmation Orientée Objet en PHP : classes, propriétés, méthodes et instanciation.

Introduction à la POO

La Programmation Orientée Objet (POO) est un paradigme de programmation qui utilise des "objets" pour modéliser des données et comportements. En PHP, la POO permet d'organiser votre code de manière plus structurée, modulaire et réutilisable.

Les principaux concepts de la POO sont :

Note : PHP supporte la POO depuis PHP 5, avec des améliorations significatives dans chaque version ultérieure. PHP 8 a notamment introduit de nouvelles fonctionnalités pour la POO comme les attributs et les types de retour union.

Créer une Classe

Une classe est une structure qui définit les propriétés et les méthodes pour un type d'objet. En PHP, on déclare une classe avec le mot-clé class :

Structure de base d'une classe
class Personne {
    // Propriétés (attributs)
    public $nom;
    public $prenom;
    protected $age;
    private $identifiant;
    
    // Constructeur (méthode spéciale)
    public function __construct($nom, $prenom, $age = 18) {
        $this->nom = $nom;
        $this->prenom = $prenom;
        $this->age = $age;
        $this->identifiant = uniqid('personne_');
    }
    
    // Méthodes
    public function sePresenter() {
        return "Bonjour, je m'appelle {$this->prenom} {$this->nom} et j'ai {$this->age} ans.";
    }
    
    public function celebrerAnniversaire() {
        $this->age++;
        return "C'est mon anniversaire! J'ai maintenant {$this->age} ans.";
    }
}

Éléments d'une classe

Modificateurs d'accès

PHP propose trois niveaux d'accès pour les propriétés et méthodes d'une classe :

public
  • Accessible de partout, à l'intérieur et à l'extérieur de la classe
  • Offre le moins d'encapsulation
  • Utile pour les propriétés et méthodes qui doivent être largement accessibles
public $nom; // Propriété publique
public function sePresenter() { // Méthode publique
    // Code
}
protected
  • Accessible à l'intérieur de la classe et dans les classes qui en héritent
  • Offre une encapsulation moyenne
  • Utile pour les propriétés et méthodes qui doivent être partagées avec les sous-classes
protected $age; // Propriété protégée
protected function calculerAge() { // Méthode protégée
    // Code
}
private
  • Accessible uniquement à l'intérieur de la classe elle-même
  • Offre le plus haut niveau d'encapsulation
  • Utile pour les propriétés et méthodes qui ne doivent pas être exposées
private $identifiant; // Propriété privée
private function genererIdentifiant() { // Méthode privée
    // Code
}

Conseil : Une bonne pratique consiste à déclarer les propriétés en private ou protected et à fournir des méthodes getter et setter pour y accéder de manière contrôlée.

Instancier des objets

Une fois que vous avez défini une classe, vous pouvez créer (ou instancier) des objets à partir de celle-ci. On utilise pour cela l'opérateur new :

Création et utilisation d'objets
// Création d'un nouvel objet
$personne1 = new Personne("Dupont", "Jean", 30);
$personne2 = new Personne("Martin", "Sophie", 25);

// Accès aux propriétés publiques
echo $personne1->nom; // Affiche "Dupont"
$personne1->nom = "Durant"; // Modification possible

// Appel des méthodes
echo $personne1->sePresenter();
// Affiche "Bonjour, je m'appelle Jean Durant et j'ai 30 ans."
echo $personne1->celebrerAnniversaire();
// Affiche "C'est mon anniversaire! J'ai maintenant 31 ans."

Exemple avec un objet Personne créé en temps réel :

Présentation initiale : Bonjour, je m'appelle Jean Dupont et j'ai 30 ans.

Après anniversaire : C'est mon anniversaire! J'ai maintenant 31 ans.

Constructeurs et Destructeurs

Les constructeurs et destructeurs sont des méthodes spéciales appelées automatiquement lors de la création et de la destruction d'un objet.

Constructeur

Le constructeur __construct() est appelé lors de la création d'un objet :

class Utilisateur {
    private $username;
    private $email;
    private $dateInscription;

    // Constructeur
    public function __construct($username, $email) {
        $this->username = $username;
        $this->email = $email;
        $this->dateInscription = date('Y-m-d H:i:s');
        echo "Nouveau compte créé pour {$username}!";
    }
}
Destructeur

Le destructeur __destruct() est appelé lorsqu'un objet est détruit :

class Connexion {
    private $connexionId;
    private $ressource;

    public function __construct() {
        $this->connexionId = uniqid('conn_');
        $this->ressource = "Ressource ouverte";
        echo "Connexion {$this->connexionId} établie.";
    }

    public function __destruct() {
        $this->ressource = null;
        echo "Connexion {$this->connexionId} fermée.";
    }
}

Getters et Setters

Les getters et setters sont des méthodes utilisées pour accéder et modifier les propriétés d'un objet. Ils permettent de contrôler l'accès aux données et d'ajouter de la validation.

Exemple de getters et setters
class Compte {
    private $solde;
    private $titulaire;
    
    public function __construct($titulaire, $soldeInitial = 0) {
        $this->titulaire = $titulaire;
        $this->setSolde($soldeInitial);
    }
    
    // Getter pour solde
    public function getSolde() {
        return $this->solde;
    }
    
    // Setter pour solde avec validation
    public function setSolde($solde) {
        if (!is_numeric($solde)) {
            throw new Exception("Le solde doit être un nombre");
        }
        $this->solde = (float) $solde;
    }
    
    // Getter pour titulaire
    public function getTitulaire() {
        return $this->titulaire;
    }
    
    // Méthodes métier
    public function deposer($montant) {
        if ($montant <= 0) {
            throw new Exception("Le montant doit être positif");
        }
        $this->solde += $montant;
        return "Dépôt de {$montant}€ effectué. Nouveau solde: {$this->solde}€";
    }
    
    public function retirer($montant) {
        if ($montant <= 0) {
            throw new Exception("Le montant doit être positif");
        }
        if ($this->solde < $montant) {
            throw new Exception("Solde insuffisant");
        }
        $this->solde -= $montant;
        return "Retrait de {$montant}€ effectué. Nouveau solde: {$this->solde}€";
    }
}

Utilisation :

// Création d'un compte
$monCompte = new Compte("Jean Dupont", 1000);

// Utilisation des getters
echo "Titulaire: " . $monCompte->getTitulaire(); // "Jean Dupont"
echo "Solde: " . $monCompte->getSolde() . "€"; // "1000€"

// Opérations bancaires
try {
    echo $monCompte->deposer(500); // "Dépôt de 500€ effectué. Nouveau solde: 1500€"
    echo $monCompte->retirer(200); // "Retrait de 200€ effectué. Nouveau solde: 1300€"
} catch (Exception $e) {
    echo "Erreur: " . $e->getMessage();
}

Important : En PHP, contrairement à d'autres langages comme Java, les getters et setters ne sont pas obligatoires. Cependant, leur utilisation est fortement recommandée pour assurer l'encapsulation et la validation des données.

Héritage

L'héritage permet de créer une nouvelle classe à partir d'une classe existante. La nouvelle classe (classe enfant) hérite des propriétés et méthodes de la classe parente.

Héritage de classes
// Classe parente
class Personne {
    protected $nom;
    protected $age;

    public function __construct($nom, $age) {
        $this->nom = $nom;
        $this->age = $age;
    }

    public function sePresenter() {
        return "Je m'appelle {$this->nom} et j'ai {$this->age} ans.";
    }
}

// Classe enfant qui hérite de Personne
class Etudiant extends Personne {
    private $formation;

    public function __construct($nom, $age, $formation) {
        // Appel du constructeur parent
        parent::__construct($nom, $age);
        $this->formation = $formation;
    }

    // Redéfinition (override) de la méthode parente
    public function sePresenter() {
        // Appel de la méthode parente
        $presentation = parent::sePresenter();
        return $presentation . " Je suis étudiant en {$this->formation}.";
    }

    public function etudier() {
        return "J'étudie la {$this->formation}.";
    }
}

Utilisation :

$personne = new Personne("Jean", 30);
echo $personne->sePresenter(); // "Je m'appelle Jean et j'ai 30 ans."

$etudiant = new Etudiant("Marie", 22, "Informatique");
echo $etudiant->sePresenter(); // "Je m'appelle Marie et j'ai 22 ans. Je suis étudiant en Informatique."
echo $etudiant->etudier(); // "J'étudie la Informatique."

Exemple avec notre classe Etudiant :

Présentation : Bonjour, je m'appelle Sophie Martin et j'ai 22 ans. Je suis étudiant en Informatique.

Moyenne : 16/20

Conseil : En PHP, une classe ne peut hériter que d'une seule classe à la fois (pas d'héritage multiple). Cependant, vous pouvez utiliser les interfaces et les traits pour obtenir un effet similaire.

Modélisation d'une classe

Voici une représentation visuelle de la classe Personne et de son héritage :

Personne
+ nom: string
+ prenom: string
# age: int
- identifiant: string
+ __construct(nom: string, prenom: string, age: int = 18): void
+ sePresenter(): string
+ celebrerAnniversaire(): string
+ getAge(): int
+ setAge(age: int): bool
+ getIdentifiant(): string
Etudiant extends Personne
- formation: string
- notes: array
+ __construct(nom: string, prenom: string, age: int, formation: string): void
+ sePresenter(): string
+ ajouterNote(matiere: string, note: float): void
+ calculerMoyenne(): float
+ getFormation(): string

Bonnes pratiques en POO

Exemple de documentation PHPDoc
/**
 * Classe représentant un produit dans une boutique en ligne
 * 
 * @author Votre Nom
 * @version 1.0
 */
class Produit {
    /**
     * Identifiant unique du produit
     * @var int
     */
    private $id;
    
    /**
     * Nom du produit
     * @var string
     */
    private $nom;
    
    /**
     * Prix du produit (en euros)
     * @var float
     */
    private $prix;
    
    /**
     * Crée une nouvelle instance de produit
     * 
     * @param string $nom  Le nom du produit
     * @param float  $prix Le prix du produit
     */
    public function __construct($nom, $prix) {
        $this->id = uniqid('prod_');
        $this->nom = $nom;
        $this->prix = $prix;
    }
    
    /**
     * Calcule le prix TTC du produit
     * 
     * @param float $tauxTva Le taux de TVA à appliquer (par défaut 20%)
     * @return float Le prix TTC du produit
     */
    public function calculerPrixTTC($tauxTva = 0.2) {
        return $this->prix * (1 + $tauxTva);
    }
}

Composition vs Héritage

Comme mentionné dans les bonnes pratiques, la composition est souvent préférable à l'héritage. Explorons ce concept fondamental de la POO.

Définition : La composition est un principe de conception où une classe contient une instance d'une autre classe comme propriété, au lieu d'hériter de celle-ci.

Ces relations peuvent être résumées comme suit :

  • Héritage : relation "EST UN" (un Etudiant est une Personne)
  • Composition : relation "A UN" (une Voiture a un Moteur)
Exemple de composition
// Classe qui sera utilisée dans la composition
class Moteur {
    private $type;
    private $cylindree;
    private $estDemarre = false;
    
    public function __construct($type, $cylindree) {
        $this->type = $type;
        $this->cylindree = $cylindree;
    }
    
    public function demarrer() {
        $this->estDemarre = true;
        return "Vroum! Le moteur {$this->type} {$this->cylindree}cc démarre.";
    }
    
    public function arreter() {
        $this->estDemarre = false;
        return "Le moteur s'arrête.";
    }
    
    public function estEnMarche() {
        return $this->estDemarre;
    }
}

// Classe qui utilise la composition
class Voiture {
    private $marque;
    private $modele;
    private $moteur; // Composition: une voiture A UN moteur
    
    public function __construct($marque, $modele, Moteur $moteur) {
        $this->marque = $marque;
        $this->modele = $modele;
        $this->moteur = $moteur; // Injection de dépendance
    }
    
    public function demarrer() {
        return "{$this->marque} {$this->modele} : " . $this->moteur->demarrer();
    }
    
    public function arreter() {
        return "{$this->marque} {$this->modele} : " . $this->moteur->arreter();
    }
    
    public function changerMoteur(Moteur $nouveauMoteur) {
        $this->moteur = $nouveauMoteur;
        return "Moteur remplacé avec succès!";
    }
    
    public function getDescription() {
        return "{$this->marque} {$this->modele}";
    }
}

Utilisation :

// Création des objets
$moteurEssence = new Moteur("essence", 1600);
$maVoiture = new Voiture("Renault", "Clio", $moteurEssence);

// Utilisation de la voiture
echo $maVoiture->getDescription(); // "Renault Clio"
echo $maVoiture->demarrer(); // "Renault Clio : Vroum! Le moteur essence 1600cc démarre."

// Création d'un nouveau moteur
$moteurElectrique = new Moteur("électrique", 0);

// Modification de la composition à l'exécution (flexibilité)
echo $maVoiture->changerMoteur($moteurElectrique); // "Moteur remplacé avec succès!"
echo $maVoiture->demarrer(); // "Renault Clio : Vroum! Le moteur électrique 0cc démarre."

Résultat du code de composition :

Description : Renault Clio

Démarrage initial : Renault Clio : Vroum! Le moteur essence 1600cc démarre.

Changement de moteur : Moteur remplacé avec succès!

Nouveau démarrage : Renault Clio : Vroum! Le moteur électrique 0cc démarre.

Avantages de la composition par rapport à l'héritage

  • Flexibilité : Les objets composés peuvent être modifiés dynamiquement à l'exécution
  • Encapsulation renforcée : Les implémentations internes peuvent changer sans affecter les utilisateurs
  • Réutilisabilité : Les composants peuvent être réutilisés dans différentes classes
  • Évite la complexité : Limite les problèmes liés aux hiérarchies d'héritage profondes
  • Relations plus claires : Définit explicitement les relations "a un" plutôt que "est un"

Rappel : "Favorisez la composition sur l'héritage" est un principe de conception fondamental. L'héritage crée un couplage fort entre les classes, alors que la composition offre plus de souplesse.

Toutefois, utilisez l'héritage lorsque la relation "est un" est véritablement appropriée et que vous souhaitez que la classe enfant hérite de tout le comportement de la classe parente.

Quand utiliser l'héritage vs la composition ?
Critère Héritage Composition
Relation "EST UN" (is-a) "A UN" (has-a)
Couplage Fort Faible
Flexibilité Limitée (définie à la compilation) Élevée (peut changer à l'exécution)
Idéal pour Spécialisation et extension de classes Réutilisation de fonctionnalités
Exemple Etudiant est une Personne Voiture a un Moteur

Pour aller plus loin

La POO en PHP offre bien d'autres fonctionnalités avancées que nous explorerons dans les prochains modules :