Programmation Orientée Objet

POO PHP

Maîtrisez la Programmation Orientée Objet en PHP : classes, objets, héritage, encapsulation, polymorphisme et design patterns modernes.

🏗️ Classes & Objets
🔗 Héritage
🛡️ Encapsulation
🎭 Polymorphisme
🎯 Design Patterns

Introduction à la POO PHP

La Programmation Orientée Objet révolutionne votre façon de coder en PHP.

🧠 Concepts Fondamentaux de la POO

Pourquoi la POO ?

Réutilisabilité : Code modulaire et réutilisable
Maintenabilité : Organisation claire du code
Extensibilité : Facile d'ajouter des fonctionnalités
Encapsulation : Protection des données
Abstraction : Masquer la complexité
Les 4 Piliers de la POO :
🏗️ Encapsulation
🔗 Héritage
🎭 Polymorphisme
🎯 Abstraction

Comparaison Procédural vs POO :

// ❌ Approche procédurale
function creerUtilisateur($nom, $email) {
// Code mélangé, difficile à maintenir
}
// ✅ Approche POO
class Utilisateur {
private $nom;
private $email;
public function __construct($nom, $email) {
$this->nom = $nom;
$this->email = $email;
}
}

Classes et Objets

Les briques fondamentales de la POO : créer et utiliser classes et objets.

🏗️ Votre Première Classe PHP

<?php
/**
 * Classe Utilisateur - Exemple de base
 * Comprend : propriétés, constructeur, méthodes
 */

class Utilisateur {
    
    // 🏷️ PROPRIÉTÉS (Variables de la classe)
    private string $nom;           // Accessible seulement dans cette classe
    private string $email;
    protected int $age;            // Accessible dans cette classe et ses enfants
    public string $statut;         // Accessible partout
    
    private array $commandes = []; // Propriété avec valeur par défaut
    
    // 🔧 CONSTRUCTEUR (Appelé lors de la création d'un objet)
    public function __construct(string $nom, string $email, int $age = 18) {
        $this->nom = $nom;
        $this->email = $email;
        $this->age = $age;
        $this->statut = 'actif';
        
        echo "✅ Nouvel utilisateur créé : {$this->nom}\n";
    }
    
    // 📖 GETTERS (Lire les propriétés privées)
    public function getNom(): string {
        return $this->nom;
    }
    
    public function getEmail(): string {
        return $this->email;
    }
    
    public function getAge(): int {
        return $this->age;
    }
    
    public function getInfos(): array {
        return [
            'nom' => $this->nom,
            'email' => $this->email,
            'age' => $this->age,
            'statut' => $this->statut
        ];
    }
    
    // ✏️ SETTERS (Modifier les propriétés privées)
    public function setNom(string $nom): void {
        if (strlen($nom) >= 2) {
            $this->nom = $nom;
        } else {
            throw new InvalidArgumentException("Le nom doit contenir au moins 2 caractères");
        }
    }
    
    public function setEmail(string $email): void {
        if (filter_var($email, FILTER_VALIDATE_EMAIL)) {
            $this->email = $email;
        } else {
            throw new InvalidArgumentException("Email invalide");
        }
    }
    
    public function setAge(int $age): void {
        if ($age >= 0 && $age <= 120) {
            $this->age = $age;
        } else {
            throw new InvalidArgumentException("Âge invalide");
        }
    }
    
    // 🎯 MÉTHODES MÉTIER (Logique de l'objet)
    public function ajouterCommande(string $produit, float $prix): void {
        $this->commandes[] = [
            'produit' => $produit,
            'prix' => $prix,
            'date' => date('Y-m-d H:i:s')
        ];
        
        echo "🛒 Commande ajoutée : {$produit} ({$prix}€)\n";
    }
    
    public function getCommandes(): array {
        return $this->commandes;
    }
    
    public function getTotalCommandes(): float {
        return array_sum(array_column($this->commandes, 'prix'));
    }
    
    public function getNombreCommandes(): int {
        return count($this->commandes);
    }
    
    public function estClient(): bool {
        return $this->getNombreCommandes() > 0;
    }
    
    public function desactiver(): void {
        $this->statut = 'inactif';
        echo "⚠️ Utilisateur {$this->nom} désactivé\n";
    }
    
    public function activer(): void {
        $this->statut = 'actif';
        echo "✅ Utilisateur {$this->nom} activé\n";
    }
    
    // 🎭 MÉTHODES MAGIQUES
    
    // Appelée lors du var_dump ou print_r
    public function __toString(): string {
        return "Utilisateur: {$this->nom} ({$this->email}) - {$this->statut}";
    }
    
    // Appelée lors de la destruction de l'objet
    public function __destruct() {
        echo "👋 Utilisateur {$this->nom} supprimé de la mémoire\n";
    }
    
    // Appelée lors de la sérialisation
    public function __sleep(): array {
        return ['nom', 'email', 'age', 'statut'];
    }
    
    // Appelée lors de la désérialisation
    public function __wakeup(): void {
        $this->commandes = [];
    }
}

// 🚀 UTILISATION DE LA CLASSE

try {
    // Créer des objets (instances de la classe)
    $user1 = new Utilisateur("Jean Dupont", "jean@email.com", 25);
    $user2 = new Utilisateur("Marie Martin", "marie@email.com"); // Âge par défaut
    
    // Utiliser les méthodes
    echo "Nom: " . $user1->getNom() . "\n";
    echo "Email: " . $user1->getEmail() . "\n";
    echo "Âge: " . $user1->getAge() . "\n";
    
    // Modifier avec les setters
    $user1->setAge(26);
    $user1->setEmail("jean.nouveau@email.com");
    
    // Ajouter des commandes
    $user1->ajouterCommande("Ordinateur", 999.99);
    $user1->ajouterCommande("Souris", 29.99);
    
    // Informations sur les commandes
    echo "Nombre de commandes: " . $user1->getNombreCommandes() . "\n";
    echo "Total des commandes: " . $user1->getTotalCommandes() . "€\n";
    echo "Est client: " . ($user1->estClient() ? 'Oui' : 'Non') . "\n";
    
    // Afficher l'objet complet
    echo $user1 . "\n"; // Utilise __toString()
    
    // Afficher toutes les infos
    print_r($user1->getInfos());
    
    // Gestion d'erreurs
    try {
        $user1->setAge(150); // Erreur
    } catch (InvalidArgumentException $e) {
        echo "❌ Erreur: " . $e->getMessage() . "\n";
    }
    
} catch (Exception $e) {
    echo "❌ Erreur générale: " . $e->getMessage() . "\n";
}

// Les objets sont détruits automatiquement à la fin du script
// __destruct() sera appelé
?>

🔍 Visibilité des Propriétés et Méthodes

🔓 Public

• Accessible partout
• Depuis l'extérieur de la classe
• Depuis les classes filles
• Depuis la classe elle-même
public $propriete;

🔐 Protected

• Accessible dans la classe
• Accessible dans les classes filles
• NON accessible depuis l'extérieur
• Idéal pour l'héritage
protected $propriete;

🔒 Private

• Accessible UNIQUEMENT dans la classe
• NON accessible depuis l'extérieur
• NON accessible dans les classes filles
• Encapsulation maximale
private $propriete;

Héritage

Créer des classes filles qui héritent des propriétés et méthodes de la classe parent.

🔗 Héritage et Classe Parent-Enfant

<?php
/**
 * HÉRITAGE EN PHP
 * Une classe fille hérite de toutes les propriétés et méthodes public/protected du parent
 */

// 🏠 CLASSE PARENT (classe de base)
class Personne {
    
    protected string $nom;
    protected string $prenom;
    protected int $age;
    private string $numeroSecu;    // Private = non hérité
    
    public function __construct(string $nom, string $prenom, int $age) {
        $this->nom = $nom;
        $this->prenom = $prenom;
        $this->age = $age;
        $this->numeroSecu = $this->genererNumeroSecu();
        
        echo "👤 Personne créée: {$this->prenom} {$this->nom}\n";
    }
    
    // Méthode accessible aux enfants (protected)
    protected function genererNumeroSecu(): string {
        return 'SEC-' . rand(100000, 999999);
    }
    
    // Méthodes publiques héritées
    public function getNomComplet(): string {
        return $this->prenom . ' ' . $this->nom;
    }
    
    public function getAge(): int {
        return $this->age;
    }
    
    public function sePresenter(): string {
        return "Je suis {$this->getNomComplet()}, {$this->age} ans.";
    }
    
    // Méthode qui peut être redéfinie (override)
    public function travailler(): string {
        return "{$this->getNomComplet()} travaille...";
    }
}

// 👨‍💻 CLASSE FILLE - Employé hérite de Personne
class Employe extends Personne {
    
    private string $poste;
    private float $salaire;
    protected string $departement;
    
    // Constructeur de la classe fille
    public function __construct(string $nom, string $prenom, int $age, string $poste, float $salaire) {
        // Appeler le constructeur du parent avec parent::
        parent::__construct($nom, $prenom, $age);
        
        $this->poste = $poste;
        $this->salaire = $salaire;
        $this->departement = 'Non assigné';
        
        echo "💼 Employé embauché: {$this->poste}\n";
    }
    
    // Nouvelles méthodes spécifiques à Employé
    public function getPoste(): string {
        return $this->poste;
    }
    
    public function getSalaire(): float {
        return $this->salaire;
    }
    
    public function setDepartement(string $departement): void {
        $this->departement = $departement;
    }
    
    public function getDepartement(): string {
        return $this->departement;
    }
    
    // OVERRIDE - Redéfinir une méthode du parent
    public function travailler(): string {
        return "{$this->getNomComplet()} travaille comme {$this->poste} au département {$this->departement}.";
    }
    
    // Méthode qui utilise une méthode du parent ET ajoute du nouveau
    public function sePresenter(): string {
        // Appeler la méthode du parent
        $presentationBase = parent::sePresenter();
        // Ajouter des infos spécifiques à l'employé
        return $presentationBase . " Je travaille comme {$this->poste}.";
    }
    
    public function calculerSalaireAnnuel(): float {
        return $this->salaire * 12;
    }
    
    public function augmentation(float $pourcentage): void {
        $ancienSalaire = $this->salaire;
        $this->salaire = $this->salaire * (1 + $pourcentage / 100);
        echo "💰 Augmentation de {$pourcentage}%: {$ancienSalaire}€ → {$this->salaire}€\n";
    }
}

// 👨‍💼 CLASSE PETITE-FILLE - Manager hérite d'Employé
class Manager extends Employe {
    
    private array $equipe = [];
    private float $bonus = 0;
    
    public function __construct(string $nom, string $prenom, int $age, float $salaire) {
        // Appeler le constructeur du parent avec un poste fixe
        parent::__construct($nom, $prenom, $age, 'Manager', $salaire);
        
        echo "🏆 Manager nommé!\n";
    }
    
    // Gestion d'équipe
    public function ajouterEmploye(Employe $employe): void {
        $this->equipe[] = $employe;
        echo "👥 {$employe->getNomComplet()} ajouté à l'équipe de {$this->getNomComplet()}\n";
    }
    
    public function getEquipe(): array {
        return $this->equipe;
    }
    
    public function getTailleEquipe(): int {
        return count($this->equipe);
    }
    
    public function setBonus(float $bonus): void {
        $this->bonus = $bonus;
    }
    
    // OVERRIDE COMPLET - Redéfinir complètement
    public function calculerSalaireAnnuel(): float {
        // Salaire de base (méthode du parent) + bonus
        $salaireBase = parent::calculerSalaireAnnuel();
        return $salaireBase + $this->bonus;
    }
    
    // OVERRIDE avec extension
    public function travailler(): string {
        $travailBase = parent::travailler();
        return $travailBase . " Et je manage une équipe de {$this->getTailleEquipe()} personnes.";
    }
    
    public function reunionEquipe(): string {
        $noms = array_map(function($emp) { return $emp->getNomComplet(); }, $this->equipe);
        return "📅 Réunion d'équipe avec: " . implode(', ', $noms);
    }
}

// 🎓 CLASSE COUSINE - Etudiant hérite aussi de Personne
class Etudiant extends Personne {
    
    private string $ecole;
    private string $filiere;
    private array $notes = [];
    
    public function __construct(string $nom, string $prenom, int $age, string $ecole, string $filiere) {
        parent::__construct($nom, $prenom, $age);
        $this->ecole = $ecole;
        $this->filiere = $filiere;
        
        echo "🎓 Étudiant inscrit: {$this->filiere} à {$this->ecole}\n";
    }
    
    public function ajouterNote(float $note): void {
        if ($note >= 0 && $note <= 20) {
            $this->notes[] = $note;
            echo "📝 Note ajoutée: {$note}/20\n";
        }
    }
    
    public function getMoyenne(): float {
        if (empty($this->notes)) return 0;
        return array_sum($this->notes) / count($this->notes);
    }
    
    // OVERRIDE pour les étudiants
    public function travailler(): string {
        return "{$this->getNomComplet()} étudie {$this->filiere} à {$this->ecole}.";
    }
    
    public function sePresenter(): string {
        $base = parent::sePresenter();
        return $base . " J'étudie {$this->filiere} à {$this->ecole}.";
    }
}

// 🚀 UTILISATION DE L'HÉRITAGE

echo "=== CRÉATION DES OBJETS ===\n";

// Créer une personne de base
$personne = new Personne("Dupont", "Jean", 30);

// Créer des employés
$employe1 = new Employe("Martin", "Marie", 25, "Développeur", 3500);
$employe2 = new Employe("Bernard", "Paul", 28, "Designer", 3200);

// Créer un manager
$manager = new Manager("Durand", "Sophie", 35, 5000);

// Créer un étudiant
$etudiant = new Etudiant("Petit", "Lucas", 20, "Université Paris", "Informatique");

echo "\n=== TESTS DES MÉTHODES ===\n";

// Toutes les classes peuvent utiliser les méthodes de Personne
echo $personne->sePresenter() . "\n";
echo $employe1->sePresenter() . "\n";
echo $manager->sePresenter() . "\n";
echo $etudiant->sePresenter() . "\n";

echo "\n=== POLYMORPHISME ===\n";

// Même méthode, comportements différents (polymorphisme)
$personnes = [$personne, $employe1, $manager, $etudiant];
foreach ($personnes as $p) {
    echo $p->travailler() . "\n";
}

echo "\n=== FONCTIONNALITÉS SPÉCIFIQUES ===\n";

// Manager gère son équipe
$manager->ajouterEmploye($employe1);
$manager->ajouterEmploye($employe2);
$manager->setDepartement("IT");
$manager->setBonus(2000);

echo $manager->reunionEquipe() . "\n";
echo "Salaire annuel du manager: " . $manager->calculerSalaireAnnuel() . "€\n";

// Étudiant et ses notes
$etudiant->ajouterNote(15.5);
$etudiant->ajouterNote(12.0);
$etudiant->ajouterNote(18.5);
echo "Moyenne de {$etudiant->getNomComplet()}: " . $etudiant->getMoyenne() . "/20\n";

// Augmentation d'employé
$employe1->setDepartement("Développement");
$employe1->augmentation(10);

echo "\n=== VÉRIFICATION DES TYPES ===\n";

// instanceof pour vérifier les types
echo "Est-ce que \$manager est un Manager? " . ($manager instanceof Manager ? 'Oui' : 'Non') . "\n";
echo "Est-ce que \$manager est un Employe? " . ($manager instanceof Employe ? 'Oui' : 'Non') . "\n";
echo "Est-ce que \$manager est une Personne? " . ($manager instanceof Personne ? 'Oui' : 'Non') . "\n";
echo "Est-ce que \$etudiant est un Employe? " . ($etudiant instanceof Employe ? 'Oui' : 'Non') . "\n";

// Fonction qui accepte n'importe quelle Personne
function faireTravailler(Personne $personne): string {
    return $personne->travailler();
}

echo "\n=== FONCTION POLYMORPHE ===\n";
foreach ($personnes as $p) {
    echo faireTravailler($p) . "\n";
}
?>

Encapsulation

Protéger les données et contrôler l'accès aux propriétés avec getters/setters.

🛡️ Exemple d'Encapsulation Complète

<?php
/**
 * ENCAPSULATION - Contrôler l'accès aux données
 * Exemple avec une classe CompteBancaire
 */

class CompteBancaire {
    
    // 🔒 Propriétés PRIVÉES - Inaccessibles de l'extérieur
    private string $numeroCpmpte;
    private float $solde;
    private string $titulaire;
    private array $historique = [];
    private bool $actif = true;
    
    // Constantes de la classe
    private const SOLDE_MINIMUM = -500.0;
    private const FRAIS_VIREMENT = 2.50;
    
    public function __construct(string $titulaire, float $soldeInitial = 0.0) {
        $this->titulaire = $titulaire;
        $this->numeroCpmpte = $this->genererNumeroCompte();
        
        // Validation du solde initial
        if ($soldeInitial < 0) {
            throw new InvalidArgumentException("Le solde initial ne peut pas être négatif");
        }
        
        $this->solde = $soldeInitial;
        $this->ajouterHistorique("Ouverture de compte", $soldeInitial);
        
        echo "🏦 Compte ouvert pour {$this->titulaire}: {$this->numeroCpmpte}\n";
    }
    
    // 🔐 Méthode privée - Utilisable seulement dans cette classe
    private function genererNumeroCompte(): string {
        return 'FR' . sprintf('%016d', mt_rand(1000000000000000, 9999999999999999));
    }
    
    private function ajouterHistorique(string $operation, float $montant, string $details = ''): void {
        $this->historique[] = [
            'date' => new DateTime(),
            'operation' => $operation,
            'montant' => $montant,
            'solde_apres' => $this->solde,
            'details' => $details
        ];
    }
    
    private function verifierCompteActif(): void {
        if (!$this->actif) {
            throw new Exception("Opération impossible: compte inactif");
        }
    }
    
    // 📖 GETTERS - Lecture contrôlée des propriétés
    public function getNumeroCompte(): string {
        return $this->numeroCpmpte;
    }
    
    public function getTitulaire(): string {
        return $this->titulaire;
    }
    
    public function getSolde(): float {
        $this->verifierCompteActif();
        return $this->solde;
    }
    
    public function estActif(): bool {
        return $this->actif;
    }
    
    // Getter calculé - pas de propriété correspondante
    public function getSoldeFormate(): string {
        return number_format($this->getSolde(), 2, ',', ' ') . ' €';
    }
    
    public function getStatut(): string {
        if (!$this->actif) return 'Inactif';
        if ($this->solde < 0) return 'Débiteur';
        if ($this->solde < 100) return 'Faible';
        return 'Normal';
    }
    
    // ✏️ SETTERS - Modification contrôlée
    public function setTitulaire(string $nouveauTitulaire): void {
        $this->verifierCompteActif();
        
        if (strlen($nouveauTitulaire) < 2) {
            throw new InvalidArgumentException("Le nom du titulaire doit contenir au moins 2 caractères");
        }
        
        $ancienTitulaire = $this->titulaire;
        $this->titulaire = $nouveauTitulaire;
        $this->ajouterHistorique("Changement de titulaire", 0, "De '{$ancienTitulaire}' vers '{$nouveauTitulaire}'");
        
        echo "✏️ Titulaire modifié: {$nouveauTitulaire}\n";
    }
    
    // 💰 MÉTHODES MÉTIER avec validation
    public function deposer(float $montant): void {
        $this->verifierCompteActif();
        
        if ($montant <= 0) {
            throw new InvalidArgumentException("Le montant doit être positif");
        }
        
        if ($montant > 10000) {
            throw new InvalidArgumentException("Dépôt maximum: 10 000€");
        }
        
        $this->solde += $montant;
        $this->ajouterHistorique("Dépôt", $montant);
        
        echo "💰 Dépôt de {$montant}€. Nouveau solde: {$this->getSoldeFormate()}\n";
    }
    
    public function retirer(float $montant): void {
        $this->verifierCompteActif();
        
        if ($montant <= 0) {
            throw new InvalidArgumentException("Le montant doit être positif");
        }
        
        $nouveauSolde = $this->solde - $montant;
        
        if ($nouveauSolde < self::SOLDE_MINIMUM) {
            throw new Exception("Retrait refusé: solde minimum autorisé " . self::SOLDE_MINIMUM . "€");
        }
        
        $this->solde = $nouveauSolde;
        $this->ajouterHistorique("Retrait", -$montant);
        
        echo "💸 Retrait de {$montant}€. Nouveau solde: {$this->getSoldeFormate()}\n";
        
        // Avertissement si le compte devient débiteur
        if ($this->solde < 0) {
            echo "⚠️ Attention: compte débiteur!\n";
        }
    }
    
    public function virer(float $montant, CompteBancaire $compteDestination): void {
        $this->verifierCompteActif();
        
        if ($montant <= 0) {
            throw new InvalidArgumentException("Le montant doit être positif");
        }
        
        $montantTotal = $montant + self::FRAIS_VIREMENT;
        
        if ($this->solde - $montantTotal < self::SOLDE_MINIMUM) {
            throw new Exception("Virement refusé: solde insuffisant (frais: " . self::FRAIS_VIREMENT . "€)");
        }
        
        // Débiter le compte source
        $this->solde -= $montantTotal;
        $this->ajouterHistorique("Virement sortant", -$montantTotal, 
            "Vers {$compteDestination->getNumeroCompte()} + frais");
        
        // Créditer le compte destination
        $compteDestination->recevoirVirement($montant, $this);
        
        echo "📤 Virement de {$montant}€ vers {$compteDestination->getTitulaire()} (frais: " . self::FRAIS_VIREMENT . "€)\n";
    }
    
    // Méthode pour recevoir un virement (peut être appelée par une autre classe)
    public function recevoirVirement(float $montant, CompteBancaire $compteSource): void {
        $this->verifierCompteActif();
        
        $this->solde += $montant;
        $this->ajouterHistorique("Virement entrant", $montant, 
            "De {$compteSource->getNumeroCompte()} ({$compteSource->getTitulaire()})");
        
        echo "📥 Virement reçu de {$montant}€ de {$compteSource->getTitulaire()}\n";
    }
    
    // 📊 MÉTHODES D'INFORMATION
    public function getHistorique(int $limite = 10): array {
        return array_slice($this->historique, -$limite);
    }
    
    public function afficherHistorique(int $limite = 5): void {
        echo "\n📋 Historique du compte {$this->numeroCpmpte} (dernières {$limite} opérations):\n";
        echo str_repeat("-", 80) . "\n";
        
        $operations = $this->getHistorique($limite);
        
        foreach ($operations as $op) {
            $date = $op['date']->format('d/m/Y H:i');
            $montant = $op['montant'] >= 0 ? "+{$op['montant']}€" : "{$op['montant']}€";
            $solde = number_format($op['solde_apres'], 2, ',', ' ') . '€';
            
            echo sprintf("%-15s | %-20s | %10s | %12s", 
                $date, $op['operation'], $montant, $solde);
                
            if ($op['details']) {
                echo " | {$op['details']}";
            }
            echo "\n";
        }
        echo str_repeat("-", 80) . "\n";
    }
    
    public function getResume(): array {
        $nombreOperations = count($this->historique);
        $depots = array_filter($this->historique, fn($op) => $op['montant'] > 0);
        $retraits = array_filter($this->historique, fn($op) => $op['montant'] < 0);
        
        return [
            'numero_compte' => $this->numeroCpmpte,
            'titulaire' => $this->titulaire,
            'solde_actuel' => $this->solde,
            'statut' => $this->getStatut(),
            'nombre_operations' => $nombreOperations,
            'total_depots' => array_sum(array_column($depots, 'montant')),
            'total_retraits' => abs(array_sum(array_column($retraits, 'montant'))),
            'derniere_operation' => end($this->historique)['date'] ?? null
        ];
    }
    
    // 🔒 MÉTHODES D'ADMINISTRATION
    public function bloquer(): void {
        $this->actif = false;
        $this->ajouterHistorique("Blocage du compte", 0);
        echo "🔒 Compte bloqué pour {$this->titulaire}\n";
    }
    
    public function debloquer(): void {
        $this->actif = true;
        $this->ajouterHistorique("Déblocage du compte", 0);
        echo "🔓 Compte débloqué pour {$this->titulaire}\n";
    }
    
    // Méthode magique pour l'affichage
    public function __toString(): string {
        return "Compte {$this->numeroCpmpte} - {$this->titulaire} - {$this->getSoldeFormate()} ({$this->getStatut()})";
    }
}

// 🚀 UTILISATION DE L'ENCAPSULATION

echo "=== CRÉATION DE COMPTES ===\n";

try {
    $compte1 = new CompteBancaire("Jean Dupont", 1000);
    $compte2 = new CompteBancaire("Marie Martin", 500);
    
    echo "\n=== OPÉRATIONS BASIQUES ===\n";
    
    // Les propriétés privées ne sont pas accessibles
    // echo $compte1->solde; // ❌ ERREUR: Cannot access private property
    
    // On utilise les getters pour lire
    echo "Solde de Jean: " . $compte1->getSoldeFormate() . "\n";
    echo "Statut: " . $compte1->getStatut() . "\n";
    
    // Opérations avec validation
    $compte1->deposer(200);
    $compte1->retirer(150);
    
    echo "\n=== MODIFICATION DU TITULAIRE ===\n";
    $compte1->setTitulaire("Jean-Pierre Dupont");
    
    echo "\n=== VIREMENT ===\n";
    $compte1->virer(100, $compte2);
    
    echo "\n=== ÉTAT DES COMPTES ===\n";
    echo $compte1 . "\n";
    echo $compte2 . "\n";
    
    echo "\n=== HISTORIQUE ===\n";
    $compte1->afficherHistorique();
    
    echo "\n=== RÉSUMÉ ===\n";
    print_r($compte1->getResume());
    
    echo "\n=== GESTION D'ERREURS ===\n";
    
    // Test des validations
    try {
        $compte1->retirer(-50); // Montant négatif
    } catch (InvalidArgumentException $e) {
        echo "❌ Erreur validation: " . $e->getMessage() . "\n";
    }
    
    try {
        $compte1->retirer(2000); // Dépassement du solde minimum
    } catch (Exception $e) {
        echo "❌ Erreur business: " . $e->getMessage() . "\n";
    }
    
    echo "\n=== BLOCAGE DE COMPTE ===\n";
    $compte2->bloquer();
    
    try {
        $compte2->deposer(100); // Sur un compte bloqué
    } catch (Exception $e) {
        echo "❌ Opération bloquée: " . $e->getMessage() . "\n";
    }
    
    $compte2->debloquer();
    $compte2->deposer(100); // Maintenant ça marche
    
} catch (Exception $e) {
    echo "❌ Erreur générale: " . $e->getMessage() . "\n";
}

echo "\n=== AVANTAGES DE L'ENCAPSULATION ===\n";
echo "✅ Données protégées contre les modifications non contrôlées\n";
echo "✅ Validation automatique des valeurs\n";
echo "✅ Historique et traçabilité\n";
echo "✅ Cohérence des données garantie\n";
echo "✅ Interface claire et simple\n";
echo "✅ Possibilité de changer l'implémentation interne sans casser le code client\n";
?>

Polymorphisme

Une même interface, plusieurs implémentations différentes.

🎭 Polymorphisme et Interfaces

<?php
/**
 * POLYMORPHISME - Une interface, plusieurs implémentations
 * Exemple avec des formes géométriques et des systèmes de paiement
 */

// 🎯 INTERFACE - Contrat que les classes doivent respecter
interface Forme {
    public function calculerAire(): float;
    public function calculerPerimetre(): float;
    public function afficher(): string;
}

// 🔴 Implémentation : Cercle
class Cercle implements Forme {
    private float $rayon;
    
    public function __construct(float $rayon) {
        $this->rayon = $rayon;
    }
    
    public function calculerAire(): float {
        return pi() * pow($this->rayon, 2);
    }
    
    public function calculerPerimetre(): float {
        return 2 * pi() * $this->rayon;
    }
    
    public function afficher(): string {
        return "🔴 Cercle (rayon: {$this->rayon})";
    }
}

// 🟦 Implémentation : Rectangle
class Rectangle implements Forme {
    private float $largeur;
    private float $hauteur;
    
    public function __construct(float $largeur, float $hauteur) {
        $this->largeur = $largeur;
        $this->hauteur = $hauteur;
    }
    
    public function calculerAire(): float {
        return $this->largeur * $this->hauteur;
    }
    
    public function calculerPerimetre(): float {
        return 2 * ($this->largeur + $this->hauteur);
    }
    
    public function afficher(): string {
        return "🟦 Rectangle ({$this->largeur} x {$this->hauteur})";
    }
}

// 🔺 Implémentation : Triangle
class Triangle implements Forme {
    private float $base;
    private float $hauteur;
    private float $cote1;
    private float $cote2;
    
    public function __construct(float $base, float $hauteur, float $cote1, float $cote2) {
        $this->base = $base;
        $this->hauteur = $hauteur;
        $this->cote1 = $cote1;
        $this->cote2 = $cote2;
    }
    
    public function calculerAire(): float {
        return ($this->base * $this->hauteur) / 2;
    }
    
    public function calculerPerimetre(): float {
        return $this->base + $this->cote1 + $this->cote2;
    }
    
    public function afficher(): string {
        return "🔺 Triangle (base: {$this->base}, hauteur: {$this->hauteur})";
    }
}

// 🏭 Classe qui utilise le polymorphisme
class CalculatriceFormes {
    private array $formes = [];
    
    public function ajouterForme(Forme $forme): void {
        $this->formes[] = $forme;
        echo "✅ Forme ajoutée: " . $forme->afficher() . "\n";
    }
    
    public function calculerAireTotal(): float {
        $total = 0;
        foreach ($this->formes as $forme) {
            $total += $forme->calculerAire(); // Polymorphisme!
        }
        return $total;
    }
    
    public function calculerPerimetreTotal(): float {
        $total = 0;
        foreach ($this->formes as $forme) {
            $total += $forme->calculerPerimetre(); // Polymorphisme!
        }
        return $total;
    }
    
    public function afficherRapport(): void {
        echo "\n📊 RAPPORT DES FORMES:\n";
        echo str_repeat("-", 50) . "\n";
        
        foreach ($this->formes as $index => $forme) {
            $aire = number_format($forme->calculerAire(), 2);
            $perimetre = number_format($forme->calculerPerimetre(), 2);
            
            echo sprintf("%d. %-30s | Aire: %8s | Périmètre: %8s\n", 
                $index + 1, 
                $forme->afficher(), 
                $aire, 
                $perimetre
            );
        }
        
        echo str_repeat("-", 50) . "\n";
        echo "TOTAL - Aire: " . number_format($this->calculerAireTotal(), 2);
        echo " | Périmètre: " . number_format($this->calculerPerimetreTotal(), 2) . "\n";
    }
}

// 💳 AUTRE EXEMPLE: Interface de paiement
interface ModePaiement {
    public function traiterPaiement(float $montant): bool;
    public function obtenirNomMethode(): string;
    public function obtenirFrais(): float;
}

class PaiementCarte implements ModePaiement {
    private string $numeroCarte;
    private const FRAIS = 0.025; // 2.5%
    
    public function __construct(string $numeroCarte) {
        $this->numeroCarte = substr($numeroCarte, -4); // Garder que les 4 derniers chiffres
    }
    
    public function traiterPaiement(float $montant): bool {
        echo "💳 Traitement paiement carte ****{$this->numeroCarte}: {$montant}€\n";
        // Simulation traitement
        sleep(1);
        echo "✅ Paiement carte approuvé\n";
        return true;
    }
    
    public function obtenirNomMethode(): string {
        return "Carte bancaire";
    }
    
    public function obtenirFrais(): float {
        return self::FRAIS;
    }
}

class PaiementPayPal implements ModePaiement {
    private string $email;
    private const FRAIS = 0.035; // 3.5%
    
    public function __construct(string $email) {
        $this->email = $email;
    }
    
    public function traiterPaiement(float $montant): bool {
        echo "🌐 Traitement paiement PayPal {$this->email}: {$montant}€\n";
        sleep(1);
        echo "✅ Paiement PayPal confirmé\n";
        return true;
    }
    
    public function obtenirNomMethode(): string {
        return "PayPal";
    }
    
    public function obtenirFrais(): float {
        return self::FRAIS;
    }
}

class PaiementVirement implements ModePaiement {
    private string $iban;
    private const FRAIS = 0.01; // 1%
    
    public function __construct(string $iban) {
        $this->iban = $iban;
    }
    
    public function traiterPaiement(float $montant): bool {
        echo "🏦 Traitement virement IBAN {$this->iban}: {$montant}€\n";
        sleep(2); // Plus lent
        echo "✅ Virement traité\n";
        return true;
    }
    
    public function obtenirNomMethode(): string {
        return "Virement bancaire";
    }
    
    public function obtenirFrais(): float {
        return self::FRAIS;
    }
}

// 🛒 Processeur de paiement polymorphe
class ProcesseurPaiement {
    
    public function effectuerPaiement(ModePaiement $mode, float $montant): array {
        echo "\n🔄 Début du traitement...\n";
        
        $frais = $montant * $mode->obtenirFrais();
        $montantTotal = $montant + $frais;
        
        echo "Méthode: {$mode->obtenirNomMethode()}\n";
        echo "Montant: {$montant}€\n";
        echo "Frais: {$frais}€\n";
        echo "Total: {$montantTotal}€\n";
        
        $success = $mode->traiterPaiement($montantTotal);
        
        return [
            'success' => $success,
            'methode' => $mode->obtenirNomMethode(),
            'montant_initial' => $montant,
            'frais' => $frais,
            'montant_total' => $montantTotal
        ];
    }
    
    public function compareMethodes(array $modesPaiement, float $montant): void {
        echo "\n💰 COMPARAISON DES MÉTHODES DE PAIEMENT pour {$montant}€:\n";
        echo str_repeat("-", 60) . "\n";
        
        foreach ($modesPaiement as $mode) {
            $frais = $montant * $mode->obtenirFrais();
            $total = $montant + $frais;
            
            echo sprintf("%-20s | Frais: %6.2f€ (%4.1f%%) | Total: %8.2f€\n",
                $mode->obtenirNomMethode(),
                $frais,
                $mode->obtenirFrais() * 100,
                $total
            );
        }
        echo str_repeat("-", 60) . "\n";
    }
}

// 🚀 UTILISATION DU POLYMORPHISME

echo "=== POLYMORPHISME AVEC LES FORMES ===\n";

$calculatrice = new CalculatriceFormes();

// Différentes formes, même interface
$calculatrice->ajouterForme(new Cercle(5));
$calculatrice->ajouterForme(new Rectangle(10, 6));
$calculatrice->ajouterForme(new Triangle(8, 4, 5, 6));
$calculatrice->ajouterForme(new Cercle(3));

// Polymorphisme: même méthode, comportements différents
$calculatrice->afficherRapport();

echo "\n=== POLYMORPHISME AVEC LES PAIEMENTS ===\n";

// Créer différents modes de paiement
$modePaiements = [
    new PaiementCarte("1234567890123456"),
    new PaiementPayPal("user@email.com"),
    new PaiementVirement("FR1234567890123456789")
];

$processeur = new ProcesseurPaiement();

// Comparer les méthodes
$processeur->compareMethodes($modePaiements, 100);

echo "\n=== TRAITEMENT DE PAIEMENTS ===\n";

// Traiter avec différentes méthodes (polymorphisme)
foreach ($modePaiements as $mode) {
    $resultat = $processeur->effectuerPaiement($mode, 100);
    
    if ($resultat['success']) {
        echo "✅ Paiement réussi avec {$resultat['methode']}\n";
    } else {
        echo "❌ Échec paiement avec {$resultat['methode']}\n";
    }
    echo "\n";
}

echo "\n=== FONCTION POLYMORPHE GÉNÉRIQUE ===\n";

// Fonction qui accepte n'importe quelle forme
function calculerEtAfficher(Forme $forme): void {
    echo $forme->afficher() . " - ";
    echo "Aire: " . number_format($forme->calculerAire(), 2);
    echo ", Périmètre: " . number_format($forme->calculerPerimetre(), 2) . "\n";
}

// Même fonction, objets différents
$formes = [
    new Cercle(7),
    new Rectangle(5, 8),
    new Triangle(6, 3, 4, 5)
];

foreach ($formes as $forme) {
    calculerEtAfficher($forme); // Polymorphisme!
}

echo "\n=== AVANTAGES DU POLYMORPHISME ===\n";
echo "✅ Code plus flexible et extensible\n";
echo "✅ Nouveaux types sans modifier le code existant\n";
echo "✅ Interface unifiée pour différentes implémentations\n";
echo "✅ Facilite les tests unitaires\n";
echo "✅ Principe ouvert/fermé respecté\n";
?>

Traits et Namespaces

Réutilisation de code avec les traits et organisation avec les namespaces.

🧩 Traits - Réutilisation de Code

<?php
/**
 * TRAITS ET NAMESPACES - Organisation et réutilisation
 * Les traits permettent de réutiliser du code dans plusieurs classes
 * Les namespaces organisent le code et évitent les conflits de noms
 */

// 📁 NAMESPACE - Organisation du code
namespace MonApp\Utils;

// 🧩 TRAIT - Code réutilisable
trait Timestampable {
    
    private ?\DateTime $dateCreation = null;
    private ?\DateTime $dateModification = null;
    
    // Méthodes du trait
    public function marquerCreation(): void {
        $this->dateCreation = new \DateTime();
        $this->dateModification = new \DateTime();
    }
    
    public function marquerModification(): void {
        $this->dateModification = new \DateTime();
    }
    
    public function getDateCreation(): ?\DateTime {
        return $this->dateCreation;
    }
    
    public function getDateModification(): ?\DateTime {
        return $this->dateModification;
    }
    
    public function getAgeEnJours(): int {
        if (!$this->dateCreation) return 0;
        return $this->dateCreation->diff(new \DateTime())->days;
    }
}

// 🔐 TRAIT - Gestion des permissions
trait Authorizable {
    
    private array $permissions = [];
    
    public function ajouterPermission(string $permission): void {
        if (!in_array($permission, $this->permissions)) {
            $this->permissions[] = $permission;
        }
    }
    
    public function retirerPermission(string $permission): void {
        $index = array_search($permission, $this->permissions);
        if ($index !== false) {
            unset($this->permissions[$index]);
            $this->permissions = array_values($this->permissions);
        }
    }
    
    public function aPermission(string $permission): bool {
        return in_array($permission, $this->permissions);
    }
    
    public function getPermissions(): array {
        return $this->permissions;
    }
    
    public function peutFaire(string $action): bool {
        return $this->aPermission($action) || $this->aPermission('admin');
    }
}

// 📊 TRAIT - Statistiques
trait Statistiques {
    
    private array $stats = [];
    
    public function incrementerStat(string $nom): void {
        if (!isset($this->stats[$nom])) {
            $this->stats[$nom] = 0;
        }
        $this->stats[$nom]++;
    }
    
    public function obtenirStat(string $nom): int {
        return $this->stats[$nom] ?? 0;
    }
    
    public function resetStat(string $nom): void {
        $this->stats[$nom] = 0;
    }
    
    public function obtenirToutesStats(): array {
        return $this->stats;
    }
}

// 👤 Classe utilisant plusieurs traits
class Utilisateur {
    
    // Utiliser plusieurs traits
    use Timestampable, Authorizable, Statistiques;
    
    private string $nom;
    private string $email;
    
    public function __construct(string $nom, string $email) {
        $this->nom = $nom;
        $this->email = $email;
        
        // Utiliser les méthodes du trait
        $this->marquerCreation();
        
        // Permissions par défaut
        $this->ajouterPermission('lire');
        $this->ajouterPermission('ecrire');
        
        echo "👤 Utilisateur créé: {$this->nom}\n";
    }
    
    public function getNom(): string { return $this->nom; }
    public function getEmail(): string { return $this->email; }
    
    public function seConnecter(): void {
        $this->marquerModification(); // Du trait Timestampable
        $this->incrementerStat('connexions'); // Du trait Statistiques
        echo "🔑 {$this->nom} s'est connecté\n";
    }
    
    public function effectuerAction(string $action): void {
        if ($this->peutFaire($action)) { // Du trait Authorizable
            $this->incrementerStat($action); // Du trait Statistiques
            echo "✅ {$this->nom} a effectué: {$action}\n";
        } else {
            echo "❌ {$this->nom} n'a pas la permission pour: {$action}\n";
        }
    }
}

// 📝 Classe Article utilisant aussi les traits
class Article {
    
    use Timestampable, Statistiques;
    
    private string $titre;
    private string $contenu;
    private string $auteur;
    
    public function __construct(string $titre, string $contenu, string $auteur) {
        $this->titre = $titre;
        $this->contenu = $contenu;
        $this->auteur = $auteur;
        
        $this->marquerCreation(); // Du trait
        echo "📝 Article créé: {$this->titre}\n";
    }
    
    public function modifier(string $nouveauContenu): void {
        $this->contenu = $nouveauContenu;
        $this->marquerModification(); // Du trait
        $this->incrementerStat('modifications'); // Du trait
        echo "✏️ Article '{$this->titre}' modifié\n";
    }
    
    public function lire(): void {
        $this->incrementerStat('lectures'); // Du trait
        echo "👁️ Article '{$this->titre}' lu\n";
    }
    
    public function getTitre(): string { return $this->titre; }
    public function getContenu(): string { return $this->contenu; }
    public function getAuteur(): string { return $this->auteur; }
}

// ==============================================
// NAMESPACE AVANCÉ
// ==============================================

namespace MonApp\Models {
    
    // Classe dans le namespace Models
    class Produit {
        private string $nom;
        private float $prix;
        
        public function __construct(string $nom, float $prix) {
            $this->nom = $nom;
            $this->prix = $prix;
        }
        
        public function getNom(): string { return $this->nom; }
        public function getPrix(): float { return $this->prix; }
    }
}

namespace MonApp\Services {
    
    // Utiliser une classe d'un autre namespace
    use MonApp\Models\Produit;
    
    class GestionnaireCommandes {
        private array $produits = [];
        
        public function ajouterProduit(Produit $produit): void {
            $this->produits[] = $produit;
        }
        
        public function calculerTotal(): float {
            $total = 0;
            foreach ($this->produits as $produit) {
                $total += $produit->getPrix();
            }
            return $total;
        }
    }
}

namespace MonApp\Utils {
    
    // Trait avec résolution de conflit
    trait Logger {
        public function log(string $message): void {
            echo "[LOG] {$message}\n";
        }
        
        public function info(string $message): void {
            echo "[INFO] {$message}\n";
        }
    }
    
    trait FileLogger {
        public function log(string $message): void {
            echo "[FILE] {$message}\n";
        }
        
        public function writeToFile(string $message): void {
            echo "[WRITING] {$message}\n";
        }
    }
    
    // Classe résolvant les conflits de traits
    class ServiceAvecLogs {
        use Logger, FileLogger {
            // Résoudre le conflit de méthode log()
            Logger::log insteadof FileLogger;
            FileLogger::log as fileLog;
        }
        
        public function faireFonctionner(): void {
            $this->log("Service démarré");        // Logger::log
            $this->fileLog("Sauvegardé");          // FileLogger::log
            $this->info("Information");            // Logger::info
            $this->writeToFile("Fichier écrit");   // FileLogger::writeToFile
        }
    }
}

// ==============================================
// UTILISATION
// ==============================================

namespace MonApp; // Namespace principal

// Importer les classes nécessaires
use MonApp\Utils\Utilisateur;
use MonApp\Utils\Article;
use MonApp\Models\Produit;
use MonApp\Services\GestionnaireCommandes;
use MonApp\Utils\ServiceAvecLogs;

echo "=== UTILISATION DES TRAITS ===\n";

// Créer un utilisateur
$user = new Utilisateur("Jean Dupont", "jean@example.com");

// Utiliser les méthodes des traits
echo "Date de création: " . $user->getDateCreation()->format('Y-m-d H:i:s') . "\n";
echo "Âge en jours: " . $user->getAgeEnJours() . "\n";

// Connexions et actions
$user->seConnecter();
$user->seConnecter();
$user->effectuerAction('lire');
$user->effectuerAction('ecrire');
$user->effectuerAction('supprimer'); // Pas autorisé

// Ajouter une permission et réessayer
$user->ajouterPermission('supprimer');
$user->effectuerAction('supprimer'); // Maintenant autorisé

echo "Permissions: " . implode(', ', $user->getPermissions()) . "\n";
echo "Statistiques: ";
print_r($user->obtenirToutesStats());

echo "\n=== ARTICLE AVEC TRAITS ===\n";

$article = new Article("Guide PHP", "Contenu de l'article", "Jean");
$article->lire();
$article->lire();
$article->modifier("Nouveau contenu");

echo "Lectures: " . $article->obtenirStat('lectures') . "\n";
echo "Modifications: " . $article->obtenirStat('modifications') . "\n";
echo "Âge de l'article: " . $article->getAgeEnJours() . " jours\n";

echo "\n=== NAMESPACES ===\n";

// Utiliser des classes de différents namespaces
$produit1 = new Produit("Ordinateur", 999.99);
$produit2 = new Produit("Souris", 29.99);

$gestionnaire = new GestionnaireCommandes();
$gestionnaire->ajouterProduit($produit1);
$gestionnaire->ajouterProduit($produit2);

echo "Total commande: " . $gestionnaire->calculerTotal() . "€\n";

echo "\n=== RÉSOLUTION DE CONFLITS DE TRAITS ===\n";

$service = new ServiceAvecLogs();
$service->faireFonctionner();

echo "\n=== AVANTAGES DES TRAITS ===\n";
echo "✅ Réutilisation de code sans héritage multiple\n";
echo "✅ Composition flexible de comportements\n";
echo "✅ Évite la duplication de code\n";
echo "✅ Possibilité de résoudre les conflits de noms\n";

echo "\n=== AVANTAGES DES NAMESPACES ===\n";
echo "✅ Organisation claire du code\n";
echo "✅ Évite les conflits de noms de classes\n";
echo "✅ Chargement automatique plus efficace\n";
echo "✅ Structure modulaire et maintenable\n";
?>

Design Patterns

Solutions éprouvées aux problèmes récurrents de conception.

🎯 Patterns Essentiels

<?php
/**
 * DESIGN PATTERNS - Solutions classiques
 * Singleton, Factory, Observer, Strategy
 */

// 1️⃣ SINGLETON - Une seule instance
class DatabaseConnection {
    
    private static ?DatabaseConnection $instance = null;
    private \PDO $connection;
    
    private function __construct() {
        // Connexion privée
        $this->connection = new \PDO('sqlite::memory:');
        echo "🔌 Connexion base de données créée\n";
    }
    
    // Empêcher le clonage
    private function __clone() {}
    
    // Empêcher la désérialisation
    public function __wakeup() {
        throw new \Exception("Cannot unserialize singleton");
    }
    
    public static function getInstance(): DatabaseConnection {
        if (self::$instance === null) {
            self::$instance = new DatabaseConnection();
        }
        return self::$instance;
    }
    
    public function query(string $sql): string {
        return "Résultat pour: {$sql}";
    }
}

// 2️⃣ FACTORY - Création d'objets
interface Transport {
    public function livrer(): string;
}

class Camion implements Transport {
    public function livrer(): string {
        return "🚛 Livraison par camion";
    }
}

class Bateau implements Transport {
    public function livrer(): string {
        return "🚢 Livraison par bateau";
    }
}

class Avion implements Transport {
    public function livrer(): string {
        return "✈️ Livraison par avion";
    }
}

class TransportFactory {
    
    public static function creerTransport(string $type): Transport {
        return match (strtolower($type)) {
            'camion', 'route' => new Camion(),
            'bateau', 'mer' => new Bateau(),
            'avion', 'air' => new Avion(),
            default => throw new \InvalidArgumentException("Type transport inconnu: {$type}")
        };
    }
}

// 3️⃣ OBSERVER - Notification d'événements
interface Observer {
    public function update(string $event, array $data): void;
}

class Subject {
    private array $observers = [];
    
    public function attach(Observer $observer): void {
        $this->observers[] = $observer;
    }
    
    public function detach(Observer $observer): void {
        $this->observers = array_filter($this->observers, fn($obs) => $obs !== $observer);
    }
    
    protected function notify(string $event, array $data = []): void {
        foreach ($this->observers as $observer) {
            $observer->update($event, $data);
        }
    }
}

class Commande extends Subject {
    private string $id;
    private string $statut = 'nouvelle';
    
    public function __construct(string $id) {
        $this->id = $id;
    }
    
    public function changerStatut(string $nouveauStatut): void {
        $this->statut = $nouveauStatut;
        $this->notify('statut_change', [
            'commande_id' => $this->id,
            'nouveau_statut' => $nouveauStatut
        ]);
    }
    
    public function getId(): string { return $this->id; }
    public function getStatut(): string { return $this->statut; }
}

class NotificateurEmail implements Observer {
    public function update(string $event, array $data): void {
        if ($event === 'statut_change') {
            echo "📧 Email envoyé pour commande {$data['commande_id']}: {$data['nouveau_statut']}\n";
        }
    }
}

class NotificateurSMS implements Observer {
    public function update(string $event, array $data): void {
        if ($event === 'statut_change') {
            echo "📱 SMS envoyé pour commande {$data['commande_id']}: {$data['nouveau_statut']}\n";
        }
    }
}

// 4️⃣ STRATEGY - Algorithmes interchangeables
interface StrategieCalcul {
    public function calculer(float $montant): float;
}

class CalculTauxNormal implements StrategieCalcul {
    public function calculer(float $montant): float {
        return $montant * 1.20; // TVA 20%
    }
}

class CalculTauxReduit implements StrategieCalcul {
    public function calculer(float $montant): float {
        return $montant * 1.055; // TVA 5.5%
    }
}

class CalculSansTaxe implements StrategieCalcul {
    public function calculer(float $montant): float {
        return $montant; // Pas de taxe
    }
}

class CalculateurTaxe {
    private StrategieCalcul $strategie;
    
    public function __construct(StrategieCalcul $strategie) {
        $this->strategie = $strategie;
    }
    
    public function setStrategie(StrategieCalcul $strategie): void {
        $this->strategie = $strategie;
    }
    
    public function calculerMontantTTC(float $montantHT): float {
        return $this->strategie->calculer($montantHT);
    }
}

// 🚀 UTILISATION DES PATTERNS

echo "=== PATTERN SINGLETON ===\n";

// Une seule instance partagée
$db1 = DatabaseConnection::getInstance();
$db2 = DatabaseConnection::getInstance();

echo ($db1 === $db2 ? "✅ Même instance" : "❌ Instances différentes") . "\n";
echo $db1->query("SELECT * FROM users") . "\n";

echo "\n=== PATTERN FACTORY ===\n";

// Création d'objets sans connaître la classe exacte
$transports = ['camion', 'bateau', 'avion'];

foreach ($transports as $type) {
    $transport = TransportFactory::creerTransport($type);
    echo $transport->livrer() . "\n";
}

echo "\n=== PATTERN OBSERVER ===\n";

// Système de notification
$commande = new Commande("CMD-001");

// Attacher des observateurs
$notifEmail = new NotificateurEmail();
$notifSMS = new NotificateurSMS();

$commande->attach($notifEmail);
$commande->attach($notifSMS);

// Changer le statut déclenche les notifications
$commande->changerStatut('payée');
$commande->changerStatut('expédiée');
$commande->changerStatut('livrée');

echo "\n=== PATTERN STRATEGY ===\n";

// Différentes stratégies de calcul
$calculateur = new CalculateurTaxe(new CalculTauxNormal());

$montantHT = 100;
echo "Montant HT: {$montantHT}€\n";

// Taux normal
echo "Taux normal (20%): " . $calculateur->calculerMontantTTC($montantHT) . "€\n";

// Changer de stratégie
$calculateur->setStrategie(new CalculTauxReduit());
echo "Taux réduit (5.5%): " . $calculateur->calculerMontantTTC($montantHT) . "€\n";

// Encore changer
$calculateur->setStrategie(new CalculSansTaxe());
echo "Sans taxe: " . $calculateur->calculerMontantTTC($montantHT) . "€\n";

echo "\n=== AVANTAGES DES PATTERNS ===\n";
echo "✅ Solutions éprouvées et testées\n";
echo "✅ Code plus maintenable et flexible\n";
echo "✅ Communication claire entre développeurs\n";
echo "✅ Évite la réinvention de la roue\n";
echo "✅ Favorise les bonnes pratiques\n";
?>

Exemples Pratiques

Applications concrètes de la POO avec des exemples complets.

🏗️ Système de Gestion E-commerce (POO Complète)

<?php
/**
 * SYSTÈME E-COMMERCE COMPLET EN POO
 * Démonstration de tous les concepts POO en pratique
 */

// 🛒 CLASSE DE BASE : Produit
abstract class Produit {
    protected string $nom;
    protected float $prix;
    protected int $stock;
    protected string $description;
    
    public function __construct(string $nom, float $prix, int $stock, string $description) {
        $this->nom = $nom;
        $this->prix = $prix;
        $this->stock = $stock;
        $this->description = $description;
    }
    
    // Méthode abstraite - doit être implémentée
    abstract public function calculerFraisLivraison(): float;
    
    // Getters/Setters
    public function getNom(): string { return $this->nom; }
    public function getPrix(): float { return $this->prix; }
    public function getStock(): int { return $this->stock; }
    
    public function estDisponible(int $quantite = 1): bool {
        return $this->stock >= $quantite;
    }
    
    public function reduireStock(int $quantite): void {
        if (!$this->estDisponible($quantite)) {
            throw new Exception("Stock insuffisant pour {$this->nom}");
        }
        $this->stock -= $quantite;
    }
    
    public function __toString(): string {
        return "{$this->nom} - {$this->prix}€ (stock: {$this->stock})";
    }
}

// 📱 Produits spécifiques héritant de Produit
class ProduitPhysique extends Produit {
    private float $poids;
    
    public function __construct(string $nom, float $prix, int $stock, string $description, float $poids) {
        parent::__construct($nom, $prix, $stock, $description);
        $this->poids = $poids;
    }
    
    public function calculerFraisLivraison(): float {
        return $this->poids * 2.50; // 2.50€ par kg
    }
    
    public function getPoids(): float { return $this->poids; }
}

class ProduitNumerique extends Produit {
    private string $urlTelechargement;
    
    public function __construct(string $nom, float $prix, int $stock, string $description, string $url) {
        parent::__construct($nom, $prix, $stock, $description);
        $this->urlTelechargement = $url;
    }
    
    public function calculerFraisLivraison(): float {
        return 0; // Pas de frais pour le numérique
    }
    
    public function getUrlTelechargement(): string { return $this->urlTelechargement; }
}

// 🛍️ PANIER avec trait
trait Timestampable {
    private DateTime $dateCreation;
    private DateTime $dateModification;
    
    protected function initTimestamps(): void {
        $this->dateCreation = new DateTime();
        $this->dateModification = new DateTime();
    }
    
    protected function updateModification(): void {
        $this->dateModification = new DateTime();
    }
    
    public function getDateCreation(): DateTime { return $this->dateCreation; }
    public function getDateModification(): DateTime { return $this->dateModification; }
}

class Panier {
    use Timestampable;
    
    private array $articles = [];
    private string $codePromo = '';
    private float $reduction = 0;
    
    public function __construct() {
        $this->initTimestamps();
    }
    
    public function ajouterProduit(Produit $produit, int $quantite = 1): void {
        if (!$produit->estDisponible($quantite)) {
            throw new Exception("Quantité non disponible pour {$produit->getNom()}");
        }
        
        $nom = $produit->getNom();
        if (isset($this->articles[$nom])) {
            $this->articles[$nom]['quantite'] += $quantite;
        } else {
            $this->articles[$nom] = [
                'produit' => $produit,
                'quantite' => $quantite
            ];
        }
        
        $this->updateModification();
        echo "🛒 Ajouté au panier: {$quantite}x {$produit->getNom()}\n";
    }
    
    public function retirerProduit(string $nom): void {
        if (isset($this->articles[$nom])) {
            unset($this->articles[$nom]);
            $this->updateModification();
            echo "🗑️ Retiré du panier: {$nom}\n";
        }
    }
    
    public function appliquerCodePromo(string $code): void {
        $codes = [
            'WELCOME10' => 0.10,
            'NOEL20' => 0.20,
            'FIDELE5' => 0.05
        ];
        
        if (isset($codes[$code])) {
            $this->codePromo = $code;
            $this->reduction = $codes[$code];
            echo "💰 Code promo appliqué: -{$this->reduction*100}%\n";
        } else {
            throw new Exception("Code promo invalide: {$code}");
        }
    }
    
    public function calculerSousTotal(): float {
        $total = 0;
        foreach ($this->articles as $article) {
            $total += $article['produit']->getPrix() * $article['quantite'];
        }
        return $total;
    }
    
    public function calculerFraisLivraison(): float {
        $frais = 0;
        foreach ($this->articles as $article) {
            $frais += $article['produit']->calculerFraisLivraison() * $article['quantite'];
        }
        return $frais;
    }
    
    public function calculerTotal(): float {
        $sousTotal = $this->calculerSousTotal();
        $fraisLivraison = $this->calculerFraisLivraison();
        $totalAvantReduction = $sousTotal + $fraisLivraison;
        
        return $totalAvantReduction * (1 - $this->reduction);
    }
    
    public function vider(): void {
        $this->articles = [];
        $this->codePromo = '';
        $this->reduction = 0;
        $this->updateModification();
    }
    
    public function getArticles(): array { return $this->articles; }
    public function getNombreArticles(): int { 
        return array_sum(array_column($this->articles, 'quantite')); 
    }
}

// 💳 INTERFACE et CLASSES de paiement
interface ProcesseurPaiement {
    public function traiterPaiement(float $montant): bool;
}

class PaiementCarte implements ProcesseurPaiement {
    public function traiterPaiement(float $montant): bool {
        echo "💳 Paiement par carte de {$montant}€ en cours...\n";
        sleep(1); // Simulation
        echo "✅ Paiement carte approuvé\n";
        return true;
    }
}

class PaiementPayPal implements ProcesseurPaiement {
    public function traiterPaiement(float $montant): bool {
        echo "🌐 Paiement PayPal de {$montant}€ en cours...\n";
        sleep(1); // Simulation
        echo "✅ Paiement PayPal confirmé\n";
        return true;
    }
}

// 📦 COMMANDE avec Observer pattern
class Commande {
    private string $numero;
    private Panier $panier;
    private string $statut = 'nouvelle';
    private DateTime $dateCommande;
    private ?DateTime $dateExpedition = null;
    private array $observers = [];
    
    public function __construct(string $numero, Panier $panier) {
        $this->numero = $numero;
        $this->panier = $panier;
        $this->dateCommande = new DateTime();
        
        echo "📝 Commande créée: {$this->numero}\n";
    }
    
    public function ajouterObserver(callable $observer): void {
        $this->observers[] = $observer;
    }
    
    private function notifierObservers(): void {
        foreach ($this->observers as $observer) {
            $observer($this->numero, $this->statut);
        }
    }
    
    public function changerStatut(string $nouveauStatut): void {
        $this->statut = $nouveauStatut;
        
        if ($nouveauStatut === 'expediee') {
            $this->dateExpedition = new DateTime();
        }
        
        $this->notifierObservers();
        echo "📊 Commande {$this->numero}: {$this->statut}\n";
    }
    
    public function traiterCommande(ProcesseurPaiement $processeur): bool {
        // Vérifier le stock et réserver
        foreach ($this->panier->getArticles() as $article) {
            $article['produit']->reduireStock($article['quantite']);
        }
        
        // Traiter le paiement
        $montant = $this->panier->calculerTotal();
        if ($processeur->traiterPaiement($montant)) {
            $this->changerStatut('payee');
            return true;
        }
        
        return false;
    }
    
    public function getNumero(): string { return $this->numero; }
    public function getStatut(): string { return $this->statut; }
    public function getMontantTotal(): float { return $this->panier->calculerTotal(); }
}

// 🏪 FACTORY pour créer les produits
class FabriqueProduits {
    public static function creerProduitPhysique(array $donnees): ProduitPhysique {
        return new ProduitPhysique(
            $donnees['nom'],
            $donnees['prix'],
            $donnees['stock'],
            $donnees['description'],
            $donnees['poids']
        );
    }
    
    public static function creerProduitNumerique(array $donnees): ProduitNumerique {
        return new ProduitNumerique(
            $donnees['nom'],
            $donnees['prix'],
            $donnees['stock'],
            $donnees['description'],
            $donnees['url']
        );
    }
}

// 🚀 UTILISATION DU SYSTÈME COMPLET

echo "=== CRÉATION DU CATALOGUE ===\n";

// Créer des produits avec la factory
$livre = FabriqueProduits::creerProduitPhysique([
    'nom' => 'Guide PHP',
    'prix' => 29.99,
    'stock' => 50,
    'description' => 'Guide complet PHP',
    'poids' => 0.5
]);

$ebook = FabriqueProduits::creerProduitNumerique([
    'nom' => 'Cours JavaScript',
    'prix' => 19.99,
    'stock' => 999,
    'description' => 'Formation JS complète',
    'url' => 'https://download.com/js-course'
]);

$ordinateur = FabriqueProduits::creerProduitPhysique([
    'nom' => 'Ordinateur Portable',
    'prix' => 899.99,
    'stock' => 10,
    'description' => 'PC portable haute performance',
    'poids' => 2.1
]);

echo "Produits créés:\n";
echo "- {$livre}\n";
echo "- {$ebook}\n"; 
echo "- {$ordinateur}\n";

echo "\n=== GESTION DU PANIER ===\n";

$panier = new Panier();

// Ajouter des produits
$panier->ajouterProduit($livre, 2);
$panier->ajouterProduit($ebook, 1);
$panier->ajouterProduit($ordinateur, 1);

// Appliquer un code promo
$panier->appliquerCodePromo('NOEL20');

// Afficher le résumé
echo "\n📋 RÉSUMÉ PANIER:\n";
echo "Articles: " . $panier->getNombreArticles() . "\n";
echo "Sous-total: " . number_format($panier->calculerSousTotal(), 2) . "€\n";
echo "Frais de livraison: " . number_format($panier->calculerFraisLivraison(), 2) . "€\n";
echo "TOTAL: " . number_format($panier->calculerTotal(), 2) . "€\n";

echo "\n=== PROCESSUS DE COMMANDE ===\n";

// Créer la commande
$commande = new Commande('CMD-' . date('Ymd') . '-001', $panier);

// Ajouter des observateurs
$commande->ajouterObserver(function($numero, $statut) {
    echo "📧 Email envoyé: Commande {$numero} est {$statut}\n";
});

$commande->ajouterObserver(function($numero, $statut) {
    echo "📱 SMS envoyé: Votre commande {$numero} est {$statut}\n";
});

// Traiter la commande avec paiement
$processeurCarte = new PaiementCarte();
if ($commande->traiterCommande($processeurCarte)) {
    $commande->changerStatut('en_preparation');
    $commande->changerStatut('expediee');
    $commande->changerStatut('livree');
}

echo "\n=== VÉRIFICATION DU STOCK ===\n";
echo "Stock après commande:\n";
echo "- {$livre}\n";
echo "- {$ebook}\n";
echo "- {$ordinateur}\n";

echo "\n=== RÉCAPITULATIF POO ===\n";
echo "✅ Classes et objets: Produit, Panier, Commande\n";
echo "✅ Héritage: ProduitPhysique/ProduitNumerique héritent de Produit\n";
echo "✅ Encapsulation: Propriétés privées avec getters/setters\n";
echo "✅ Polymorphisme: ProcesseurPaiement avec différentes implémentations\n";
echo "✅ Abstraction: Classe abstraite Produit\n";
echo "✅ Interface: ProcesseurPaiement\n";
echo "✅ Traits: Timestampable pour horodatage\n";
echo "✅ Factory: FabriqueProduits\n";
echo "✅ Observer: Notifications de changement de statut\n";
?>

POO PHP Maîtrisée !

Vous maîtrisez maintenant la Programmation Orientée Objet en PHP. Vos applications seront plus robustes, maintenables et évolutives !

🎯 Compétences POO acquises :

Classes & Objets • Héritage • Encapsulation • Polymorphisme • Interfaces • Traits • Namespaces • Design Patterns • Applications Pratiques