Kodokon kodokon.com

Héritage, interfaces, traits et classes abstraites

Choisissez le bon outil de conception - contrat, squelette d'algorithme ou réutilisation horizontale - en connaissant les compromis de chacun.

9 min · 3 questions

Ouvrir cette leçon dans Kodokon

PHP vous donne quatre outils de conception qui se recouvrent partiellement : héritage concret, classes abstraites, interfaces et traits. Le choix se résume à une question : que voulez-vous partager ? Un contrat : interface. Un contrat plus un squelette d'implémentation : classe abstraite. Du code entre classes sans lien hiérarchique : trait. L'héritage concret, lui, se justifie de moins en moins : il couple fortement, et PHP n'autorise qu'un seul parent.

PHP
<?php

declare(strict_types=1);

abstract class Exporter
{
    public function export(array $rows): string
    {
        return $this->head() . $this->body($rows);
    }

    abstract protected function head(): string;

    abstract protected function body(
        array $rows
    ): string;
}

final class CsvExporter extends Exporter
{
    protected function head(): string
    {
        return "id;name\n";
    }

    protected function body(array $rows): string
    {
        $lines = array_map(
            fn (array $r): string => implode(';', $r),
            $rows,
        );

        return implode("\n", $lines);
    }
}

$csv = new CsvExporter();
echo $csv->export([[1, 'Ada'], [2, 'Linus']]);
Patron de méthode : le parent fixe l'algorithme, les filles fournissent les détails.

L'interface est le contrat pur : des signatures et des constantes, ni état ni implémentation. Son avantage décisif sur la classe abstraite : une classe peut en implémenter plusieurs, et n'importe quelle classe existante peut adopter le contrat après coup. La classe abstraite, elle, transporte de l'état et du code partagé, mais consomme votre unique emplacement d'héritage. En pratique : typez vos paramètres avec des interfaces (Cache, pas ArrayCache) pour pouvoir substituer les implémentations, en test comme en production.

PHP
<?php

declare(strict_types=1);

interface Cache
{
    public function get(string $key): ?string;

    public function set(
        string $key,
        string $value
    ): void;
}

final class ArrayCache implements Cache
{
    private array $items = [];

    public function get(string $key): ?string
    {
        return $this->items[$key] ?? null;
    }

    public function set(
        string $key,
        string $value
    ): void {
        $this->items[$key] = $value;
    }
}

$cache = new ArrayCache();
$cache->set('lang', 'PHP');
echo $cache->get('lang');
Le code client dépend de Cache : l'implémentation reste interchangeable.

Les traits résolvent le partage horizontal : injecter les mêmes méthodes dans des classes sans ancêtre commun. Quand deux traits fournissent une méthode du même nom, PHP refuse de choisir à votre place et exige un arbitrage explicite : insteadof désigne la version conservée, as crée un alias pour garder l'autre accessible. C'est verbeux à dessein - le conflit doit rester visible dans le code.

PHP
<?php

declare(strict_types=1);

trait FileLogger
{
    public function log(string $message): void
    {
        echo "[file] $message\n";
    }
}

trait AuditLogger
{
    public function log(string $message): void
    {
        echo "[audit] $message\n";
    }
}

final class PaymentService
{
    use FileLogger, AuditLogger {
        FileLogger::log insteadof AuditLogger;
        AuditLogger::log as audit;
    }
}

$service = new PaymentService();
$service->log('Payment received');
$service->audit('Payment received');
Conflit de traits : insteadof tranche, as conserve un alias.

Quiz de validation

Vérifiez que vous avez bien retenu les points clés de cette leçon.

  1. Quel avantage décisif l'interface a-t-elle sur la classe abstraite ?
    • Elle peut contenir des propriétés partagées
    • Une classe peut implémenter plusieurs interfaces, mais n'hériter que d'une seule classe
    • Ses méthodes s'exécutent plus rapidement
  2. Deux traits utilisés par la même classe déclarent chacun une méthode log(). Que se passe-t-il ?
    • PHP utilise la méthode du premier trait listé
    • Erreur fatale, sauf arbitrage explicite avec insteadof
    • PHP fusionne les deux méthodes en une seule
  3. Que peut contenir une classe abstraite qu'une interface ne peut pas contenir ?
    • Des constantes
    • Des propriétés et des méthodes implémentées
    • Des signatures de méthodes publiques