Aller au contenu
M07 · B. Programmer en PHP

Héritage, classes abstraites, interfaces et polymorphisme

Réutiliser une classe avec extends, appeler le parent (parent::), redéfinir une méthode, imposer un contrat avec abstract et interface, traiter des objets différents de la même façon, et charger les classes automatiquement.

1Situer

Souk Atlas propose plusieurs modes de livraison : standard, express, retrait au dépôt. Ils partagent beaucoup (une ville, un délai, un résumé) mais calculent leurs frais différemment. L'héritage évite de copier le code commun, et le polymorphisme permet d'écrire une seule boucle pour tous les modes.

2Comprendre

L'héritage : Express hérite de Standard
abstract Livraison resume() abstract frais(), delai() Standard frais() 35 DH · delai() 3 Express frais() parent + 40 · delai() 1 new Livraison(...) ✖ Cannot instantiate abstract class parent::frais() → 3535 + 40 → 75 $e->resume()écrite dans Livraison,frais() pris dans Express Express vers Tanger :75 DH, 1 jour(s)

Livraison est abstraite : elle fournit resume() mais laisse frais() et delai() à ses enfants. On ne peut pas faire new Livraison.

1 / 5

Hériter d’une classe

class Produit
{
    public function __construct(protected string $nom, protected float $prix) {}
    public function etiquette(): string
    {
        return "{$this->nom} : {$this->prix} DH";
    }
}

class ProduitSolde extends Produit
{
    public function __construct(string $nom, float $prix, private int $remise)
    {
        parent::__construct($nom, $prix);
    }

    public function etiquette(): string                       // redéfinition
    {
        $nouveau = $this->prix * (100 - $this->remise) / 100;
        return parent::etiquette() . " → $nouveau DH (-{$this->remise} %)";
    }
}

echo (new ProduitSolde("Babouches", 150, 20))->etiquette();

Classes et méthodes abstraites

Dans la simulation, Livraison impose frais() et delai(), mais écrit une fois pour toutes resume(), qui les utilise. Chaque mode n’a plus qu’à donner ses deux règles.

Interfaces

interface Exportable
{
    public function versCsv(): string;
}

class Commande implements Exportable, JsonSerializable
{
    public function __construct(private int $numero, private float $total) {}
    public function versCsv(): string { return "{$this->numero};{$this->total}"; }
    public function jsonSerialize(): array { return ['numero' => $this->numero, 'total' => $this->total]; }
}

JsonSerializable est une interface fournie par PHP : json_encode($commande) appelle automatiquement jsonSerialize(). On s’en servira pour les API (leçon D.2).

Le polymorphisme

$modes = [new Standard("Fès", 320), new Express("Tanger", 320), new Retrait("Safi", 320)];
foreach ($modes as $m) {
    echo $m->resume(), "<br>";      // chaque objet utilise SES frais() et delai()
}

$m instanceof Express permet de tester la classe d’un objet si besoin, mais le polymorphisme sert justement à l’éviter : pour ajouter un mode « Relais », on écrit une nouvelle classe sans toucher à la boucle.

Charger les classes automatiquement

spl_autoload_register(function (string $classe) {
    require __DIR__ . "/classes/$classe.php";
});

$p = new Produit("Savon", 30);   // classes/Produit.php est inclus à cet instant
Exemple simple

La famille des véhicules

Un camion et un triporteur sont tous deux des véhicules de livraison : ils ont une immatriculation et peuvent démarrer (hérité), mais chacun calcule sa charge maximale à sa façon (redéfini).

Exemple simple

La prise électrique

Une interface est comme une prise normalisée : peu importe l'appareil, s'il a la bonne fiche, il se branche. Toute classe qui implémente Payable doit avoir une méthode payer().

3Manipuler

Les modes de livraison héritent de la classe abstraite Livraison. Complétez Express.php et Retrait.php, puis observez la boucle polymorphe de index.php.
Pas à pas dans le lab ↗ Cliquez sur Exécuter : PHP 8.3 démarre dans votre navigateur (environ 5 Mo à télécharger, une seule fois).

La page s'affichera ici.

Le navigateur demandera /index.php au serveur, qui exécutera le PHP et renverra du HTML.

Entre le navigateur et le serveur aucun échange pour l'instant

Consignes 0 / 3

  1. « Standard vers Fès : 35 DH, 3 jour(s) » et la livraison offerte à Casablanca (650 DH)
  2. « Express vers Tanger : 75 DH, 1 jour(s) »
  3. « Retrait vers Safi : 0 DH, 2 jour(s) »

4Pratiquer

Exercice 1 · Une interface Payable

Déclarez une interface Payable avec une méthode montant(): float, et deux classes Commande et Abonnement qui l'implémentent. Écrivez une fonction totalAPayer(array $elements) qui additionne les montants.

Voir le corrigé
<?php
interface Payable
{
    public function montant(): float;
}
class Commande implements Payable
{
    public function __construct(private float $total) {}
    public function montant(): float { return $this->total; }
}
class Abonnement implements Payable
{
    public function __construct(private int $mois, private float $prixMois) {}
    public function montant(): float { return $this->mois * $this->prixMois; }
}
function totalAPayer(array $elements): float
{
    $t = 0;
    foreach ($elements as $e) {
        $t += $e->montant();      // peu importe la classe : elle a montant()
    }
    return $t;
}
echo totalAPayer([new Commande(330), new Abonnement(3, 49)]);   // 477

Exercice 2 · Appeler le constructeur parent

ProduitArtisanal hérite de Produit(nom, prix) et ajoute la propriété artisan. Écrivez son constructeur.

Voir le corrigé
<?php
class ProduitArtisanal extends Produit
{
    public function __construct(string $nom, float $prix, public string $artisan)
    {
        parent::__construct($nom, $prix);   // on laisse Produit initialiser ses propriétés
    }
}

Exercice 3 · instanceof

Dans une liste de livraisons, affichez « (gratuit) » seulement pour les objets de type Retrait.

Voir le corrigé
<?php
foreach ($modes as $m) {
    echo $m->resume();
    if ($m instanceof Retrait) {
        echo " (gratuit)";
    }
    echo "<br>";
}

5Vérifier

1. Peut-on écrire new Livraison(...) si Livraison est abstract ?
2. Une classe PHP peut hériter de combien de classes ?
3. Que fait parent::frais() dans une méthode de la classe fille ?