L'héritage en POO (que ce soit en C++, Java ou autre langage utilisant la POO) est une technique très puissante et extrêmement pratique. Ce chapitre sur l'héritage est le chapitre à connaitre par cœur (ou du moins, le mieux possible). Pour être bien sûr que vous ayez compris le principe, un TP vous attend au prochain chapitre.
Allez, j'arrête de vous mettre la pression, allons-y !
Notion d'héritage
Définition
Quand on parle d'héritage, c'est qu'on dit qu'une classe B hérite d'une classe A. La classe A est donc considérée comme la classe mère et la classe B est considérée comme la classe fille.
Concrètement, l'héritage, c'est quoi ?
Lorsqu'on dit que la classe B hérite de la classe A, c'est que la classe B hérite de tous les attributs et méthodes de la classe A. Si l'on déclare des méthodes dans la classe A, et qu'on crée une instance de la classe B, alors on pourra appeler n'importe quelle méthode déclarée dans la classe A du moment qu'elle est publique.
Schématiquement, une classe B héritant d'une classe A peut être représentée comme ceci (figure suivante).

Vous voyez que la classe Magicien
a hérité de toutes les méthodes et d'aucun attribut de la classe Personnage
. Souvenez-vous : toutes les méthodes sont publiques et tous les attributs sont privés. En fait, les attributs privés ont bien été hérités aussi, mais notre classe Magicien
ne pourra s'en servir, c'est la raison pour laquelle je ne les ai pas représentés. Il n'y a que les méthodes de la classe parente qui auront accès à ces attributs. C'est comme pour le principe d'encapsulation : ici, les éléments privés sont masqués. On dit que la classe Personnage
est la classe mère et que la classe Magicien
est la classe fille.
Quand est-ce que je sais si telle classe doit hériter d'une autre ?
Soit deux classes A
et B
. Pour qu'un héritage soit possible, il faut que vous puissiez dire que A
est un B
. Par exemple, un magicien est un personnage, donc héritage. Un chien est un animal, donc héritage aussi. Bref, vous avez compris le principe.
Procéder à un héritage
Pour procéder à un héritage (c'est-à-dire faire en sorte qu'une classe hérite des attributs et méthodes d'une autre classe), il suffit d'utiliser le mot-clé extends
. Vous déclarez votre classe comme d'habitude (class MaClasse
) en ajoutant extends NomDeLaClasseAHeriter
comme ceci :
<?php
class Personnage // Création d'une classe simple.
{
}
class Magicien extends Personnage // Notre classe Magicien hérite des attributs et méthodes de Personnage.
{
}
Comme dans la réalité, une mère peut avoir plusieurs filles, mais une fille ne peut avoir plusieurs mères. La seule différence avec la vie réelle, c'est qu'une mère ne peut avoir une infinité de filles.
Ainsi, on pourrait créer des classes Magicien
, Guerrier
, Brute
, etc., qui héritent toutes de Personnage
: la classe Personnage
sert de modèle.
<?php
class Personnage // Création d'une classe simple.
{
}
// Toutes les classes suivantes hériteront de Personnage.
class Magicien extends Personnage
{
}
class Guerrier extends Personnage
{
}
class Brute extends Personnage
{
}
Ainsi, toutes ces nouvelles classes auront les mêmes attributs et méthodes quePersonnage
.
Super, tu me crées des classes qui sont exactement les mêmes qu'une autre… Très utile ! En plus, tout ce qui est privé, je n'y ai pas accès donc…
Nous sommes d'accord, si l'héritage c'était ça, ce serait le concept le plus idiot et le plus inutile de la POO.
Chaque classe peut créer des attributs et méthodes qui lui seront propres, et c'est là toute la puissance de l'héritage : toutes les classes que l'on a créées plus haut peuvent avoir des attributs et méthodes en plus des attributs et méthodes hérités. Pour cela, rien de plus simple. Il vous suffit de créer des attributs et méthodes comme nous avons appris jusqu'à maintenant. Un exemple ?
<?php
class Magicien extends Personnage
{
private $_magie; // Indique la puissance du magicien sur 100, sa capacité à produire de la magie.
public function lancerUnSort($perso)
{
$perso->recevoirDegats($this->_magie); // On va dire que la magie du magicien représente sa force.
}
}
Ainsi, la classe Magicien
aura, en plus des attributs et méthodes hérités, un attribut $magie
et une méthode lancerUnSort
.
Redéfinir les méthodes
On vient de créer une classe Magicien
héritant de toutes les méthodes de la classe Personnage
. Que diriez-vous si l'on pouvait réécrire certaines méthodes, afin de modifier leur comportement ? Pour cela, il vous suffit de déclarer à nouveau la méthode et d'écrire ce que bon vous semble à l'intérieur.
Un problème se pose pourtant. Si vous voulez accéder à un attribut de la classe parente pour le modifier, vous ne pourrez pas car notre classe Magicien
n'a pas accès aux attributs de sa classe mère Personnage
puisqu'ils sont tous privés.
Nous allons maintenant essayer de redéfinir la méthode gagnerExperience
afin de modifier l'attribut stockant la magie (admettons $_magie) lorsque, justement, on gagne de l'expérience. Problème : si on la réécrit, nous écrasons toutes les instructions présentes dans la méthode de la classe parente (Personnage
), ce qui aura pour effet de ne pas faire gagner d'expérience à notre magicien mais juste de lui augmenter sa magie. Solution : appeler la méthode gagnerExperience
de la classe parente, puis ensuite ajouter les instructions modifiant la magie.
Il suffit pour cela d'utiliser le mot-clé parent
suivi du symbole double deux points, suivi lui-même du nom de la méthode à appeler.
<?php
class Magicien extends Personnage
{
private $_magie; // Indique la puissance du magicien sur 100, sa capacité à produire de la magie.
public function lancerUnSort($perso)
{
$perso->recevoirDegats($this->_magie); // On va dire que la magie du magicien représente sa force.
}
public function gagnerExperience()
{
// On appelle la méthode gagnerExperience() de la classe parente
parent::gagnerExperience();
if ($this->_magie < 100)
{
$this->_magie += 10;
}
}
}
Notez que si la méthode parente retourne une valeur, vous pouvez la récupérer comme si vous appeliez une méthode normalement. Exemple :
<?php
class A
{
public function test()
{
return 'test';
}
}
class B extends A
{
public function test()
{
$retour = parent::test();
echo $retour;
}
}
$b = new B;
$b->test(); // Affiche "test"
Comme vous pouvez le constater, j'ai fait appel aux getters et setters correspondant à l'attribut $_magie. Pourquoi ? Car les classes enfant n'ont pas accès aux éléments privés, il fallait donc que la classe parente le fasse pour moi ! Il n'y a que les méthodes de la classe parente non réécrites qui ont accès aux éléments privés. À partir du moment où l'on réécrit une méthode de la classe parente, la méthode appartient à la classe fille et n'a donc plus accès aux éléments privés.
Héritez à l'infini !
Toute classe en POO peut être héritée si elle ne spécifie pas le contraire, vraiment toutes. Vous pouvez ainsi reproduire un arbre réel avec autant de classes héritant les unes des autres que vous le souhaitez.
Pour reprendre l'exemple du magicien dans le cours sur la POO en C++ de M@teo21, on peut créer deux autres classes MagicienBlanc
et MagicienNoir
qui héritent toutes les deux de Magicien
. Exemple :
<?php
class Personnage // Classe Personnage de base.
{
}
class Magicien extends Personnage // Classe Magicien héritant de Personnage.
{
}
class MagicienBlanc extends Magicien // Classe MagicienBlanc héritant de Magicien.
{
}
class MagicienNoir extends Magicien // Classe MagicienNoir héritant de Magicien.
{
}
Et un petit schéma qui reproduit ce code (figure suivante).

Ainsi, les classes MagicienBlanc
et MagicienNoir
hériteront de tous les attributs et de toutes les méthodes des classes Magicien
et Personnage
.
Un nouveau type de visibilité : protected
Je vais à présent vous présenter le dernier type de visibilité existant en POO : il s'agit de protected
. Ce type de visibilité est, au niveau restrictif, à placer entre public
et private
.
Je vous rappelle brièvement les rôles de ces deux portées de visibilité :
Public
: ce type de visibilité nous laisse beaucoup de liberté. On peut accéder à l'attribut ou à la méthode de n'importe où. Toute classe fille aura accès aux éléments publics.Private
: ce type est celui qui nous laisse le moins de liberté. On ne peut accéder à l'attribut ou à la méthode que depuis l'intérieur de la classe qui l'a créé. Toute classe fille n'aura pas accès aux éléments privés.
Le type de visibilité protected
est en fait une petite modification du type private
: il a exactement les mêmes effets que private
, à l'exception que toute classe fille aura accès aux éléments protégés.
Exemple :
<?php
class ClasseMere
{
protected $attributProtege;
private $_attributPrive;
public function __construct()
{
$this->attributProtege = 'Hello world !';
$this->_attributPrive = 'Bonjour tout le monde !';
}
}
class ClasseFille extends ClasseMere
{
public function afficherAttributs()
{
echo $this->attributProtege; // L'attribut est protégé, on a donc accès à celui-ci.
echo $this->_attributPrive; // L'attribut est privé, on n'a pas accès celui-ci, donc rien ne s'affichera (mis à part une notice si vous les avez activées).
}
}
$obj = new ClasseFille;
echo $obj->attributProtege; // Erreur fatale.
echo $obj->_attributPrive; // Rien ne s'affiche (ou une notice si vous les avez activées).
$obj->afficherAttributs(); // Affiche « Hello world ! » suivi de rien du tout ou d'une notice si vous les avez activées.
Comme vous pouvez le constater, il n'y a pas d'underscores précédant les noms d'éléments protégés. C'est encore une fois la notation PEAR qui nous dit que les noms d'éléments protégés ne sont pas précédés de ce caractère.
Et pour le principe d'encapsulation, j'utilise quoi ? private
ou protected
?
La portée private
est, selon moi, bien trop restrictive et contraignante. Elle empêche toute classe enfant d'accéder aux attributs et méthodes privées alors que cette dernière en a souvent besoin. De manière générale, je vous conseille donc de toujours mettre protected
au lieu de private
, à moins que vous teniez absolument à ce que la classe enfant ne puisse y avoir accès (utile si vous comptez partager votre classe).
Imposer des contraintes
Il est possible de mettre en place des contraintes. On parlera alors d'abstraction ou de finalisation suivant la contrainte instaurée.
Abstraction
Classes abstraites
On a vu jusqu'à maintenant que l'on pouvait instancier n'importe quelle classe afin de pouvoir exploiter ses méthodes. On va maintenant découvrir comment empêcher quiconque d'instancier telle classe.
Mais à quoi ça sert de créer une classe si on ne peut pas s'en servir ?
On ne pourra pas se servir directement de la classe. La seule façon d'exploiter ses méthodes est de créer une classe héritant de la classe abstraite.
Vous vous demandez sans doute à quoi cela peut bien servir. L'exemple que je vais prendre est celui du personnage et de ses classes filles. Dans ce que nous venons de faire, nous ne créerons jamais d'objet Personnage
, mais uniquement des objets Magicien
, Guerrier
, Brute
, etc. En effet, à quoi cela nous servirait d'instancier la classe Personnage
si notre but est de créer un tel type de personnage ?
Nous allons donc considérer la classe Personnage
comme étant une classe modèle dont toute classe fille possédera les méthodes et attributs.
Pour déclarer une classe abstraite, il suffit de faire précéder le mot-clé class
du mot-clé abstract
comme ceci :
<?php
abstract class Personnage // Notre classe Personnage est abstraite.
{
}
class Magicien extends Personnage // Création d'une classe Magicien héritant de la classe Personnage.
{
}
$magicien = new Magicien; // Tout va bien, la classe Magicien n'est pas abstraite.
$perso = new Personnage; // Erreur fatale car on instancie une classe abstraite.
Simple et court à retenir, il suffit juste de se souvenir où l'on doit le placer.
Ainsi, si vous essayez de créer une instance de la classe Personnage
, une erreur fatale sera levée. Ceci nous garantit que l'on ne créera jamais d'objet Personnage
(suite à une étourderie par exemple).
Méthodes abstraites
Si vous décidez de rendre une méthode abstraite en plaçant le mot-clé abstract
juste avant la visibilité de la méthode, vous forcerez toutes les classes filles à écrire cette méthode. Si tel n'est pas le cas, une erreur fatale sera levée. Puisque l'on force la classe fille à écrire la méthode, on ne doit spécifier aucune instruction dans la méthode, on déclarera juste son prototype (visibilité + function
+ nomDeLaMethode + parenthèses avec ou sans paramètres + point-virgule).
<?php
abstract class Personnage
{
// On va forcer toute classe fille à écrire cette méthode car chaque personnage frappe différemment.
abstract public function frapper(Personnage $perso);
// Cette méthode n'aura pas besoin d'être réécrite.
public function recevoirDegats()
{
// Instructions.
}
}
class Magicien extends Personnage
{
// On écrit la méthode « frapper » du même type de visibilité que la méthode abstraite « frapper » de la classe mère.
public function frapper(Personnage $perso)
{
// Instructions.
}
}
Finalisation
Classes finales
Le concept des classes et méthodes finales est exactement l'inverse du concept d'abstraction. Si une classe est finale, vous ne pourrez pas créer de classe fille héritant de cette classe.
Pour ma part, je ne rends jamais mes classes finales (au même titre que, à quelques exceptions près, je mets toujours mes attributs en protected
) pour me laisser la liberté d'hériter de n'importe quelle classe.
Pour déclarer une classe finale, vous devez placer le mot-clé final
juste avant le mot-clé class
, comme abstract
.
<?php
// Classe abstraite servant de modèle.
abstract class Personnage
{
}
// Classe finale, on ne pourra créer de classe héritant de Guerrier.
final class Guerrier extends Personnage
{
}
// Erreur fatale, car notre classe hérite d'une classe finale.
class GentilGuerrier extends Guerrier
{
}
Méthodes finales
Si vous déclarez une méthode finale, toute classe fille de la classe comportant cette méthode finale héritera de cette méthode mais ne pourra la redéfinir. Si vous déclarez votre méthode recevoirDegats
en tant que méthode finale, vous ne pourrez la redéfinir.
<?php
abstract class Personnage
{
// Méthode normale.
public function frapper(Personnage $perso)
{
// Instructions.
}
// Méthode finale.
final public function recevoirDegats()
{
// Instructions.
}
}
class Guerrier extends Personnage
{
// Aucun problème.
public function frapper(Personnage $perso)
{
// Instructions.
}
// Erreur fatale car cette méthode est finale dans la classe parente.
public function recevoirDegats()
{
// Instructions.
}
}
Résolution statique à la volée
Cette section va vous montrer une possibilité intéressante de la POO en PHP : la résolution statique à la volée. C'est une notion un peu complexe à comprendre au premier abord, alors n'hésitez pas à relire plusieurs fois.
Effectuons d'abord un petit flash-back sur self::
. Vous vous souvenez à quoi il sert ? À appeler un attribut, une méthode statique ou une constante de la classe dans laquelle est contenu self::
. Ainsi, si vous testez ce code :
<?php
class Mere
{
public static function lancerLeTest()
{
self::quiEstCe();
}
public static function quiEstCe()
{
echo 'Je suis la classe <strong>Mere</strong> !';
}
}
class Enfant extends Mere
{
public static function quiEstCe()
{
echo 'Je suis la classe <strong>Enfant</strong> !';
}
}
Enfant::lancerLeTest();
À l'écran s'affichera :

Mais qu'est-ce qu'il s'est passé ???
appel de la méthode
lancerLeTest
de la classeEnfant
;la méthode n'a pas été réécrite, on va donc « chercher » la méthode
lancerLeTest
de la classe mère ;appel de la méthode
quiEstCe
de la classeMere
.
Pourquoi c'est la méthode quiEstCe
de la classe parente qui a été appelée ? Pourquoi pas celle de la classe fille puisqu'elle a été récrite ?
Tout simplement parce que self::
fait appel à la méthode statique de la classe dans laquelle est contenu self::
, donc de la classe parente.
Et la résolution statique à la volée dans tout ça ?
Tout tourne autour de l'utilisation de static::
. static::
a exactement le même effet que self::
, à l'exception près que static::
appelle l'élément de la classe qui est appelée pendant l'exécution. C'est-à-dire que si j'appelle la méthode lancerLeTest
depuis la classe Enfant
et que dans cette méthode j'utilise static::
au lieu de self::
, c'est la méthode quiEstCe
de la classe Enfant
qui sera appelée et non de la classe Mere
!
<?php
class Mere
{
public static function lancerLeTest()
{
static::quiEstCe();
}
public function quiEstCe()
{
echo 'Je suis la classe <strong>Mere</strong> !';
}
}
class Enfant extends Mere
{
public static function quiEstCe()
{
echo 'Je suis la classe <strong>Enfant</strong> !';
}
}
Enfant::lancerLeTest();
Ce qui donnera :

<?php
class Mere
{
public function lancerLeTest()
{
static::quiEstCe();
}
public function quiEstCe()
{
echo 'Je suis la classe « Mere » !';
}
}
class Enfant extends Mere
{
public function quiEstCe()
{
echo 'Je suis la classe « Enfant » !';
}
}
$e = new Enfant;
$e->lancerLeTest();
Cas complexes
Au premier abord, vous n'avez peut-être pas tout compris. Si tel est le cas, ne lisez pas la suite cela risquerait de vous embrouiller davantage. Prenez bien le temps de comprendre ce qui est écrit plus haut puis vous pourrez continuer.
Comme le spécifie le titre, il y a quelques cas complexes (des pièges en quelque sorte). Imaginons trois classes A, B et C qui héritent chacune d'une autre (A est la grand-mère, B la mère et C la fille). En PHP, on dirait plutôt :
<?php
class A
{
}
class B extends A
{
}
class C extends B
{
}
Nous allons implémenter dans chacune des classes une méthode qui aura pour rôle d'afficher le nom de la classe pour pouvoir effectuer quelques tests.
<?php
class A
{
public function quiEstCe()
{
echo 'A';
}
}
class B extends A
{
public function quiEstCe()
{
echo 'B';
}
}
class C extends B
{
public function quiEstCe()
{
echo 'C';
}
}
Créons une méthode de test dans la classe B. Pourquoi dans cette classe ? Parce qu'elle hérite de A et est héritée par C, son cas est donc intéressant à étudier.
Appelons maintenant cette méthode depuis la classe C dans un contexte statique (nul besoin de créer d'objet, mais ça marche tout aussi bien ).
<?php
class A
{
public function quiEstCe()
{
echo 'A';
}
}
class B extends A
{
public static function test()
{
}
public function quiEstCe()
{
echo 'B';
}
}
class C extends B
{
public function quiEstCe()
{
echo 'C';
}
}
C::test();
Plaçons un peu de code dans cette méthode, sinon ce n'est pas drôle.
Nous allons essayer d'appeler la méthode parentequiEstCe
. Là, il n'y a pas de piège, pas de résolution statique à la volée, donc à l'écran s'affichera « A » :
<?php
class A
{
public function quiEstCe()
{
echo 'A';
}
}
class B extends A
{
public static function test()
{
parent::quiEstCe();
}
public function quiEstCe()
{
echo 'B';
}
}
class C extends B
{
public function quiEstCe()
{
echo 'C';
}
}
C::test();
Maintenant, créons une méthode dans la classe A qui sera chargée d'appeler la méthodequiEstCe
avecstatic::
. Là, si vous savez ce qui va s'afficher, vous avez tout compris !
<?php
class A
{
public function appelerQuiEstCe()
{
static::quiEstCe();
}
public function quiEstCe()
{
echo 'A';
}
}
class B extends A
{
public static function test()
{
parent::appelerQuiEstCe();
}
public function quiEstCe()
{
echo 'B';
}
}
class C extends B
{
public function quiEstCe()
{
echo 'C';
}
}
C::test();
?>
Alors ? Vous avez une petite idée ? À l'écran s'affichera… C ! Décortiquons ce qui s'est passé :
Appel de la méthode
test
de la classeC
;la méthode n'a pas été réécrite, on appelle donc la méthode
test
de la classeB
;on appelle maintenant la méthode
appelerQuiEstCe
de la classeA
(avecparent::
) ;résolution statique à la volée : on appelle la méthode
quiEstCe
de la classe qui a appelé la méthodeappelerQuiEstCe
;la méthode
quiEstCe
de la classeC
est donc appelée car c'est depuis la classeC
qu'on a appelé la méthodetest
.
C'est très compliqué mais fondamental à comprendre.
Remplaçons maintenantparent::
parself::
:
<?php
class A
{
public function appelerQuiEstCe()
{
static::quiEstCe();
}
public function quiEstCe()
{
echo 'A';
}
}
class B extends A
{
public static function test()
{
self::appelerQuiEstCe();
}
public function quiEstCe()
{
echo 'B';
}
}
class C extends B
{
public function quiEstCe()
{
echo 'C';
}
}
C::test();
Et là, qu'est-ce qui s'affiche à l'écran ? Et bien toujours C ! Le principe est exactement le même que le code plus haut.
<?php
class A
{
public static function appelerQuiEstCe()
{
static::quiEstCe();
}
public function quiEstCe()
{
echo 'A';
}
}
class B extends A
{
public static function test()
{
// On appelle « appelerQuiEstCe » de la classe « A » normalement.
A::appelerQuiEstCe();
}
public function quiEstCe()
{
echo 'B';
}
}
class C extends B
{
public function quiEstCe()
{
echo 'C';
}
}
C::test();
Utilisation de static:: dans un contexte non statique
L'utilisation destatic::
dans un contexte non statique se fait de la même façon que dans un contexte statique. Je vais prendre l'exemple de la documentation pour illustrer mes propos :
<?php
class TestParent
{
public function __construct()
{
static::qui();
}
public static function qui()
{
echo 'TestParent';
}
}
class TestChild extends TestParent
{
public function __construct()
{
static::qui();
}
public function test()
{
$o = new TestParent();
}
public static function qui()
{
echo 'TestChild';
}
}
$o = new TestChild;
$o->test();
À l'écran s'affichera « TestChild » suivi de « TestParent ». Je vous explique ce qui s'est passé si vous n'avez pas tout suivi :
Création d'une instance de la classe
TestChild
;appel de la méthode
qui
de la classeTestChild
puisque c'est la méthode__construct
de la classeTestChild
qui a été appelée ;appel de la méthode
test
de la classeTestChild
;création d'une instance de la classe
TestParent
;appel de la méthode
qui
de la classeTestParent
puisque c'est la méthode__construct
de cette classe qui a été appelée.
Ouf ! Enfin terminé !
N'hésitez pas à le relire autant de fois que nécessaire afin de bien comprendre cette notion d'héritage et toutes les possibilités que ce concept vous offre. Ne soyez pas pressés de continuer si vous n'avez pas tout compris, sinon vous allez vous planter au TP.
En résumé
Nous pouvons parler d'héritage entre une classe A et une classe B si et seulement si nous pouvons dire « B est un A » (dans ce cas-là, B hérite de A).
Une classe héritant d'une autre a accès à tous ses attributs et méthodes publics et protégés.
La visibilité
protected
est équivalente à la visibilitéprivate
à la différence près qu'un élément protégé est accessible par les classes filles, contrairement aux éléments privés.Il est possible d'interdire l'instanciation d'une classe grâce au mot-clé
abstract
(utile pour poser un modèle de base commun à plusieurs classes) et l'héritage d'une classe grâce au mot-cléfinal
.Il est possible d'obliger ses classes filles à implémenter une méthode grâce au mot-clé
abstract
et de leur interdire la réécriture d'une méthode grâce au mot-cléfinal
.La résolution statique à la volée permet de savoir quelle classe a été initialement appelée afin d'invoquer des méthodes ou accéder à des attributs de la classe appelée.