Maîtrisez la Programmation Orientée Objet en PHP : classes, objets, héritage, encapsulation, polymorphisme et design patterns modernes.
La Programmation Orientée Objet révolutionne votre façon de coder en PHP.
Les briques fondamentales de la POO : créer et utiliser classes et objets.
<?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é
?>
Créer des classes filles qui héritent des propriétés et méthodes de la classe parent.
<?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";
}
?>
Protéger les données et contrôler l'accès aux propriétés avec getters/setters.
<?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";
?>
Une même interface, plusieurs implémentations différentes.
<?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";
?>
Réutilisation de code avec les traits et organisation avec les namespaces.
<?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";
?>
Solutions éprouvées aux problèmes récurrents de conception.
<?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";
?>
Applications concrètes de la POO avec des exemples complets.
<?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";
?>
Vous maîtrisez maintenant la Programmation Orientée Objet en PHP. Vos applications seront plus robustes, maintenables et évolutives !
Classes & Objets • Héritage • Encapsulation • Polymorphisme • Interfaces • Traits • Namespaces • Design Patterns • Applications Pratiques