Cours RechercherCtrl K

PHP Leçon 8

Héritage et fichiers séparés

Ranger chaque classe dans son fichier, créer des classes enfants avec extends, et signaler une erreur avec une exception.

30 min

À la fin de cette leçon, tu sauras

  • Ranger une classe dans son propre fichier et l'inclure avec require_once
  • Créer une classe enfant avec extends et appeler le parent avec parent::
  • Choisir entre private et protected
  • Lancer et attraper une exception avec throw, try et catch

Une classe, un fichier

Dans un vrai projet, on ne met pas tout dans index.php. L'habitude est de ranger chaque classe dans son propre fichier, qui porte le même nom qu'elle : la classe Compte va dans Compte.php. Le fichier principal l'inclut avec require_once :

<?php
// Fichier Compte.php : uniquement la classe
class Compte
{
    public function __construct(public string $titulaire, protected float $solde = 0)
    {
    }

    public function decrire(): string
    {
        return "$this->titulaire : $this->solde €";
    }
}
<?php
// Fichier index.php
require_once 'Compte.php';      // copie le contenu de Compte.php ici

$compte = new Compte("Léa", 150);
echo $compte->decrire();
  • require inclut un fichier ; s'il n'existe pas, le programme s'arrête avec une erreur fatale ;
  • include fait pareil, mais continue avec un simple avertissement si le fichier manque ;
  • la version _once (require_once, include_once) n'inclut le fichier qu'une seule fois, même si on le demande plusieurs fois. C'est celle qu'on utilise pour les classes : déclarer deux fois la même classe est une erreur.

Dans les exercices : ils ont plusieurs fichiers, avec des onglets au-dessus de l'éditeur. ▶ Exécuter lance toujours index.php, qui inclut les autres.

L'héritage : extends

Un livret d'épargne est un compte, avec un taux d'intérêt en plus. Plutôt que de tout réécrire, on crée une classe enfant qui hérite de la classe parente avec extends : elle reçoit toutes ses propriétés et méthodes, et peut en ajouter.

<?php
class Livret extends Compte
{
    public function __construct(string $titulaire, float $solde, private float $taux)
    {
        parent::__construct($titulaire, $solde);    // on laisse le parent faire sa part
    }

    public function ajouterInterets(): void
    {
        $this->solde += $this->solde * $this->taux / 100;
    }
}

$livret = new Livret("Tom", 1000, 3);
$livret->ajouterInterets();
echo $livret->decrire();        // Tom : 1030 €  (decrire() vient de Compte)
  • parent::__construct(…) appelle le constructeur de la classe parente, pour qu'elle remplisse ses propres propriétés ;
  • Livret n'a pas écrit decrire() : elle l'a reçue de Compte.

private ou protected ?

Une propriété private n'est visible que dans sa classe : même une classe enfant ne peut pas la lire. C'est pour ça que $solde est protected dans Compte : protected = visible dans la classe et dans ses classes enfants, mais toujours pas depuis l'extérieur.

MotDans la classeDans une classe enfantAilleurs
publicouiouioui
protectedouiouinon
privateouinonnon

Redéfinir une méthode

Une classe enfant peut redéfinir une méthode du parent en écrivant une méthode du même nom. Avec parent::nom(), elle peut quand même réutiliser la version du parent :

<?php
class Livret extends Compte
{
    // … constructeur et ajouterInterets() comme plus haut …

    public function decrire(): string
    {
        return parent::decrire() . " (taux : $this->taux %)";
    }
}

echo $livret->decrire();     // Tom : 1030 € (taux : 3 %)

Signaler une erreur : les exceptions

Quand une méthode reçoit une valeur impossible (un dépôt de -50 €), le plus propre est de lancer une exception avec throw. Le programme s'arrête net… sauf si le code qui a appelé la méthode l'attrape avec try / catch.

<?php
class Compte
{
    // …
    public function deposer(float $montant): void
    {
        if ($montant <= 0) {
            throw new InvalidArgumentException("Montant invalide : $montant");
        }
        $this->solde += $montant;
    }
}

try {
    $compte->deposer(-50);
    echo "Dépôt fait";               // jamais exécuté : l'exception saute directement au catch
} catch (InvalidArgumentException $e) {
    echo "Erreur : " . $e->getMessage();   // Erreur : Montant invalide : -50
}

InvalidArgumentException est une classe d'exception fournie par PHP (« argument invalide »). $e est l'objet exception, et sa méthode getMessage() renvoie le texte donné à new.

À retenir

  • Une classe par fichier, au même nom (Compte.php), inclus avec require_once 'Compte.php';.
  • require arrête tout si le fichier manque, include continue ; _once évite d'inclure deux fois.
  • class Livret extends Compte : la classe enfant hérite de tout et peut ajouter ou redéfinir des méthodes.
  • parent::__construct(…) et parent::methode() appellent la version de la classe parente.
  • protected = visible dans la classe et ses enfants ; private = seulement dans la classe.
  • throw new InvalidArgumentException("…") lance une exception ; try { … } catch (InvalidArgumentException $e) { $e->getMessage(); } l'attrape.

Voir la fiche de révision de cette leçon (à imprimer ou à relire avant un contrôle)

Exercice 1 · Ranger la classe dans son fichier

Facile

Cet exercice a deux fichiers. La classe Produit est déjà écrite dans Produit.php, mais index.php ne la connaît pas encore : clique sur ▶ Exécuter, tu verras l'erreur « Class "Produit" not found ».

  1. en haut de index.php, inclus Produit.php avec require_once ;
  2. crée ensuite un produit « Clavier » à 49 € ;
  3. affiche sa description avec la méthode decrire().
Résultat

Clique sur ▶ Exécuter (ou Ctrl + Entrée) pour lancer ton code.

Consignes à réussir

  • À faire : index.php inclut Produit.php avec require_once
  • À faire : Un produit est créé avec new Produit(…)
  • À faire : La classe reste dans Produit.php
  • À faire : La page affiche « Clavier : 49 € » ▶ vérifié quand tu exécutes
Toujours bloqué ? Voir une solution

index.php

<?php
require_once 'Produit.php';

$produit = new Produit("Clavier", 49);
echo $produit->decrire();

Exercice 2 · Une classe enfant

Moyen

La classe Animal (dans Animal.php) a un nom protégé et une méthode decrire(). Dans Chien.php :

  1. déclare une classe Chien qui hérite d'Animal ;
  2. ajoute-lui une méthode aboyer() qui renvoie « Rex dit Wouf ! » (en utilisant le nom de l'animal, pas en écrivant « Rex » en dur).

Puis, dans index.php, inclus les deux fichiers (Animal.php d'abord, car Chien en a besoin), et le code existant affichera les deux phrases.

Résultat

Clique sur ▶ Exécuter (ou Ctrl + Entrée) pour lancer ton code.

Consignes à réussir

  • À faire : Chien hérite d'Animal (extends)
  • À faire : aboyer() utilise $this->nom
  • À faire : index.php inclut Animal.php puis Chien.php
  • À faire : La page affiche « Je m'appelle Rex. » puis « Rex dit Wouf ! » ▶ vérifié quand tu exécutes
Toujours bloqué ? Voir une solution

index.php

<?php
require_once 'Animal.php';
require_once 'Chien.php';

$rex = new Chien("Rex");
echo "<p>" . $rex->decrire() . "</p>";
echo "<p>" . $rex->aboyer() . "</p>";

Chien.php

<?php
class Chien extends Animal
{
    public function aboyer(): string
    {
        return "$this->nom dit Wouf !";
    }
}

Exercice 3 · Livret d'épargne et exceptions

Pour aller plus loin

Compte.php contient la classe Compte. À toi d'écrire :

  1. dans Compte.php, complète deposer() : si le montant est négatif ou nul, lance une InvalidArgumentException avec le message « Montant invalide » ;
  2. dans Livret.php, une classe Livret qui hérite de Compte, avec un constructeur qui reçoit le titulaire, le solde et un taux (propriété privée), et qui appelle parent::__construct ;
  3. toujours dans Livret, redéfinis decrire() pour ajouter « (taux : 3 %) » à la description du parent, avec parent::decrire().

index.php est déjà écrit : il doit afficher « Tom : 1000 € (taux : 3 %) » puis « Erreur : Montant invalide ».

Résultat

Clique sur ▶ Exécuter (ou Ctrl + Entrée) pour lancer ton code.

Consignes à réussir

  • À faire : deposer() lance une InvalidArgumentException
  • À faire : Livret hérite de Compte
  • À faire : Le constructeur appelle parent::__construct
  • À faire : decrire() réutilise parent::decrire()
  • À faire : La page affiche « Tom : 1000 € (taux : 3 %) » ▶ vérifié quand tu exécutes
  • À faire : L'exception est attrapée : « Erreur : Montant invalide » ▶ vérifié quand tu exécutes
Toujours bloqué ? Voir une solution

Compte.php

<?php
class Compte
{
    public function __construct(public string $titulaire, protected float $solde = 0)
    {
    }

    public function deposer(float $montant): void
    {
        if ($montant <= 0) {
            throw new InvalidArgumentException("Montant invalide");
        }
        $this->solde += $montant;
    }

    public function decrire(): string
    {
        return "$this->titulaire : $this->solde €";
    }
}

Livret.php

<?php
class Livret extends Compte
{
    public function __construct(string $titulaire, float $solde, private float $taux)
    {
        parent::__construct($titulaire, $solde);
    }

    public function decrire(): string
    {
        return parent::decrire() . " (taux : $this->taux %)";
    }
}

Quiz

1Pourquoi utilise-t-on require_once plutôt que require pour inclure une classe ?
2Que fait class Chat extends Animal ?
3Une propriété doit être lue par les classes enfants mais pas depuis l'extérieur. Quel mot choisir ?
4Après throw dans un try, où continue le programme ?