extends : hériter d'une classe
Un CompteEpargne est un Compte avec un taux en plus. En PHP, on l'écrit avec extends : la classe fille récupère toutes les propriétés et méthodes public/protected du parent.
class Compte {
protected float $solde = 0; // protected → accessible aux filles
public function deposer(float $m): void { if ($m > 0) $this->solde += $m; }
public function getSolde(): float { return $this->solde; }
}
class CompteEpargne extends Compte {
public function __construct(private float $taux) {}
public function ajouterInterets(): void {
$this->deposer($this->solde * $this->taux); // réutilise la méthode héritée
}
}
Remarque : $solde est protected (pas private) pour rester accessible à la classe fille. private resterait invisible même pour les enfants.
public/protected du parent, puis ajoutent ou redéfinissent.parent:: et la redéfinition
Une fille peut redéfinir (override) une méthode du parent, et appeler la version parente avec parent::. Si la fille a son propre constructeur, elle doit appeler parent::__construct(...) pour initialiser la partie héritée.
Imaginez ce code : class Animal { function decrire() { return "Je suis un animal"; } } et class Chien extends Animal { function decrire() { return parent::decrire() . " et un chien"; } }. Avant de dérouler : que renvoie (new Chien())->decrire(), à quoi sert parent::decrire() exactement, et que se passerait-il si on l'enlevait ?
Voir la réponse
Chien::decrire() remplace (override) la méthode héritée. parent::decrire() appelle explicitement la version d'Animal depuis l'enfant pour la compléter, au lieu de la jeter. (new Chien())->decrire() renvoie "Je suis un animal et un chien". Sans parent::, le comportement parent disparait : il ne resterait que " et un chien" (ou il faudrait tout réécrire). C'est le mécanisme pour étendre une méthode plutôt que la remplacer entièrement.
class CompteCourant extends Compte {
public function __construct(private float $decouvertAutorise) {
parent::__construct(); // construit la partie "Compte"
}
// on REDÉFINIT retirer pour autoriser un découvert
public function retirer(float $m): bool {
if ($m > 0 && $m <= $this->solde + $this->decouvertAutorise) {
$this->solde -= $m;
return true;
}
return false;
}
}
Les classes abstraites : un gabarit
Une classe abstraite ne peut pas être instanciée directement : c'est un gabarit pour ses filles. Elle peut imposer des méthodes abstraites (sans corps) que chaque fille doit implémenter.
abstract class Forme {
abstract public function aire(): float; // pas de corps : à implémenter
public function decrire(): string { // méthode concrète partagée
return "Aire = " . $this->aire();
}
}
class Carre extends Forme {
public function __construct(private float $cote) {}
public function aire(): float { return $this->cote ** 2; }
}
// new Forme(); // ❌ Error: Cannot instantiate abstract class Forme
Exécute : héritage en action
<?php
class Compte {
protected float $solde = 0;
public function deposer(float $m): void { if ($m > 0) $this->solde += $m; }
public function getSolde(): float { return $this->solde; }
}
class CompteEpargne extends Compte {
public function __construct(private float $taux) {}
public function ajouterInterets(): void {
$this->deposer($this->solde * $this->taux);
}
}
$e = new CompteEpargne(0.05);
$e->deposer(1000); // méthode HÉRITÉE de Compte
$e->ajouterInterets(); // méthode PROPRE → +50
echo "Solde : " . $e->getSolde() . " €\n"; // 1050
?>
final : interdire l'héritage ou la redéfinition
L'héritage et la redéfinition sont puissants, mais parfois on veut garantir qu'une classe ou une méthode ne sera pas modifiée par une sous-classe. PHP offre le mot-clé final exactement pour ça.
final class Paiement { … }: la classe ne peut pas être étendue. Tenterclass X extends Paiementprovoque une erreur fatale à la compilation (Class X cannot extend final class Paiement).final public function valider() { … }dans une classe non-finale : la méthode ne peut pas être redéfinie par une fille. La tenter provoque Cannot override final method.
<?php
// final class : aucune classe ne peut l'étendre
final class Paiement {
public function __construct(private float $montant) {}
public function valider(): string {
return "Paiement de {$this->montant} € validé.";
}
}
// Utilisation normale : fonctionne parfaitement
$p = new Paiement(49.90);
echo $p->valider() . "\n";
// Ce qui suit provoquerait une erreur fatale à la compilation :
// class PaiementFraude extends Paiement {}
// Fatal error: Class PaiementFraude cannot extend final class Paiement
// final sur une méthode dans une classe non-finale
class Compte {
protected float $solde = 0;
public function deposer(float $m): void { if ($m > 0) $this->solde += $m; }
final public function getSolde(): float { return $this->solde; }
// getSolde() ne peut PAS être redéfinie par une classe fille
}
class CompteDemo extends Compte {
// final public function getSolde(): float { return 0; }
// => Fatal error: Cannot override final method Compte::getSolde()
}
$c = new CompteDemo();
$c->deposer(200);
echo "Solde : " . $c->getSolde() . " €\n";
?>
✦ Bonne pratique : final est un choix de conception délibéré, pas une obligation. Réservez-le aux classes critiques (logique de paiement, sécurité, invariants métier stricts) ou aux méthodes dont le comportement ne doit jamais varier dans les sous-classes. Évitez de mettre final partout par réflexe : cela réduit la flexibilité et complique les tests (impossible de créer un mock qui hérite).
extends: inheriting from a class
A CompteEpargne is a Compte with an extra rate. In PHP, you write it with extends: the child class gets all the parent's public/protected properties and methods.
class Compte {
protected float $solde = 0; // protected → accessible to children
public function deposer(float $m): void { if ($m > 0) $this->solde += $m; }
public function getSolde(): float { return $this->solde; }
}
class CompteEpargne extends Compte {
public function __construct(private float $taux) {}
public function ajouterInterets(): void {
$this->deposer($this->solde * $this->taux); // reuses the inherited method
}
}
Note: $solde is protected (not private) so it stays accessible to the child class. private would be invisible even to children.
public/protected members, then add or override.parent:: and overriding
A child can override a parent method, and call the parent version with parent::. If the child has its own constructor, it must call parent::__construct(...) to initialize the inherited part.
Consider this code: class Animal { function decrire() { return "Je suis un animal"; } } and class Chien extends Animal { function decrire() { return parent::decrire() . " et un chien"; } }. Before scrolling down: what does (new Chien())->decrire() return, what exactly does parent::decrire() do, and what would happen if you removed it?
See the answer
Chien::decrire() overrides (replaces) the inherited method. parent::decrire() explicitly calls the Animal version from the child to extend it, rather than throwing it away. (new Chien())->decrire() returns "Je suis un animal et un chien". Without parent::, the parent behaviour is lost: only " et un chien" would remain (or you'd have to rewrite everything). This is the mechanism to extend a method rather than replace it entirely.
class CompteCourant extends Compte {
public function __construct(private float $decouvertAutorise) {
parent::__construct(); // builds the "Compte" part
}
// we OVERRIDE retirer to allow an overdraft
public function retirer(float $m): bool {
if ($m > 0 && $m <= $this->solde + $this->decouvertAutorise) {
$this->solde -= $m;
return true;
}
return false;
}
}
Abstract classes: a template
An abstract class can't be instantiated directly: it's a template for its children. It can require abstract methods (no body) that each child must implement.
abstract class Forme {
abstract public function aire(): float; // no body: must be implemented
public function decrire(): string { // shared concrete method
return "Area = " . $this->aire();
}
}
class Carre extends Forme {
public function __construct(private float $cote) {}
public function aire(): float { return $this->cote ** 2; }
}
// new Forme(); // ❌ Error: Cannot instantiate abstract class Forme
Run it: inheritance in action
<?php
class Compte {
protected float $solde = 0;
public function deposer(float $m): void { if ($m > 0) $this->solde += $m; }
public function getSolde(): float { return $this->solde; }
}
class CompteEpargne extends Compte {
public function __construct(private float $taux) {}
public function ajouterInterets(): void {
$this->deposer($this->solde * $this->taux);
}
}
$e = new CompteEpargne(0.05);
$e->deposer(1000); // method INHERITED from Compte
$e->ajouterInterets(); // OWN method → +50
echo "Balance: " . $e->getSolde() . " €\n"; // 1050
?>
final: prevent inheritance or overriding
Inheritance and overriding are powerful, but sometimes you want to guarantee that a class or method won't be changed by a subclass. PHP provides the final keyword exactly for that.
final class Paiement { … }: the class cannot be extended. Attemptingclass X extends Paiementtriggers a fatal compile-time error (Class X cannot extend final class Paiement).final public function valider() { … }in a non-final class: the method cannot be overridden by a child. Any attempt throws Cannot override final method.
<?php
// final class: no class can extend it
final class Paiement {
public function __construct(private float $montant) {}
public function valider(): string {
return "Payment of {$this->montant} € validated.";
}
}
// Normal use: works perfectly
$p = new Paiement(49.90);
echo $p->valider() . "\n";
// The following would cause a fatal compile-time error:
// class PaiementFraude extends Paiement {}
// Fatal error: Class PaiementFraude cannot extend final class Paiement
// final on a method inside a non-final class
class Compte {
protected float $solde = 0;
public function deposer(float $m): void { if ($m > 0) $this->solde += $m; }
final public function getSolde(): float { return $this->solde; }
// getSolde() CANNOT be overridden by any child class
}
class CompteDemo extends Compte {
// final public function getSolde(): float { return 0; }
// => Fatal error: Cannot override final method Compte::getSolde()
}
$c = new CompteDemo();
$c->deposer(200);
echo "Balance: " . $c->getSolde() . " €\n";
?>
✦ Best practice: final is a deliberate design choice, not a default. Reserve it for critical classes (payment logic, security, strict business invariants) or methods whose behaviour must never vary in subclasses. Avoid marking everything final by reflex: it reduces flexibility and makes testing harder (you can't create a mock that inherits).
🎯 Pratique
S'entraîner (clique pour ouvrir) :
⚖️ Juge le code de l'IA
Tu veux un compte de support avec un plafond de retrait. L'IA te propose de faire hériter CompteSupport de CompteEpargne et passe $solde en public. Tu acceptes tel quel, ou tu rejettes ? Justifie.
class CompteSupport extends CompteEpargne { // CompteEpargne a déjà ajouterInterets()
public float $solde = 0; // public : on y accède de partout
public function retirer(float $m): void {
if ($m <= 1000) $this->solde -= $m; // plafond codé en dur
}
}
public float $solde casse l'encapsulation : n'importe quel code peut écrire $compte->solde = 999999 sans passer par deposer(). Le parent l'avait à juste titre en protected ; ici on l'expose pour rien. 2) Héritage abusif : un compte de support n'est pas un compte épargne, il hérite donc d'ajouterInterets() qui n'a aucun sens pour lui. La règle « est-un » n'est pas respectée. Le bon réflexe : garder $solde en protected et faire hériter CompteSupport de Compte (ou d'une classe/interface adaptée), pas d'CompteEpargne.🧠 Rappel libre
Sans remonter dans la leçon : pourquoi met-on $solde en protected plutôt qu'en private dans Compte, et qu'est-ce qui empêche d'écrire new Forme() sur une classe abstraite ?
protected rend $solde accessible à la classe fille (ex. CompteEpargne qui lit $this->solde dans ajouterInterets()) ; private le rendrait invisible même pour les enfants. Pour la classe abstraite : abstract class Forme ne peut pas être instanciée parce qu'elle déclare au moins une méthode abstraite sans corps (aire()) ; PHP lève « Cannot instantiate abstract class Forme ». Elle sert de gabarit : seules ses filles, qui implémentent aire(), sont instanciables.parent::__construct(...) ?abstract class Forme déclare abstract public function aire(): float;. Que doivent faire les classes Cercle et Carre qui l'étendent, et pourquoi est-il impossible d'écrire new Forme() ?L'abstraction impose un contrat. PHP a un outil dédié pour ça, plus souple que l'héritage : les interfaces.
Leçon 4 : Interfaces →