Choisissez le bon outil de conception - contrat, squelette d'algorithme ou réutilisation horizontale - en connaissant les compromis de chacun.
Ouvrir cette leçon dans KodokonPHP 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
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']]);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
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');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
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');log(). Que se passe-t-il ?insteadof