Programmazione Orientata agli Oggetti con le Interfacce PHP
Le interfacce PHP sono fondamentali nella OOP: definiscono contratti di metodi, favoriscono il disaccoppiamento e supportano l'implementazione multipla.
Un'interfaccia è un contratto che elenca i metodi che una classe deve fornire, senza specificare come tali metodi funzionano. Consente a classi non correlate di concordare un insieme condiviso di comportamenti, in modo che il resto del codice possa fare affidamento sul contratto piuttosto che su una classe specifica. Questo capitolo spiega come dichiarare le interfacce, implementarle, usare le costanti, estenderle e combinarle, e quali errori comuni evitare.
Se sei nuovo agli oggetti, leggi prima Classi e Oggetti PHP e Cos'è la OOP in PHP.
Cos'è un'Interfaccia in PHP?
Un'interfaccia definisce un insieme di firme di metodi pubblici — nomi, parametri e (facoltativamente) tipi di ritorno — ma nessun corpo di metodo. Qualsiasi classe che implementa l'interfaccia si impegna a fornire un corpo concreto per ognuno di quei metodi. In caso contrario, PHP genera un errore fatale.
Poiché l'interfaccia specifica solo cosa una classe può fare e mai come, è possibile sostituire un'implementazione con un'altra senza modificare il codice che le utilizza. Questo disaccoppiamento è lo scopo principale.
<?php
interface Logger {
public function log(string $message): void;
}Regole fondamentali per i membri dell'interfaccia:
- Tutti i metodi sono implicitamente pubblici e astratti — non è possibile assegnare loro un corpo, né contrassegnarli come
privateoprotected. - Un'interfaccia può dichiarare costanti, ma non proprietà.
- Una classe segnala che soddisfa il contratto con la parola chiave
implements.
Perché Usare le Interfacce?
- Contratti. Garantiscono che qualsiasi classe che le implementa esponga un'API concordata, così i chiamanti sanno esattamente quali metodi sono disponibili.
- Disaccoppiamento. Il codice può essere scritto contro l'interfaccia (
Logger) anziché contro una classe concreta (FileLogger), consentendo di cambiare implementazioni liberamente — un file logger reale in produzione, uno fittizio nei test. - Tipi multipli. A differenza dell'ereditarietà di classe, una classe può implementare molte interfacce, consentendole di svolgere più ruoli contemporaneamente.
- Polimorfismo. Oggetti diversi che condividono un'interfaccia possono essere usati in modo intercambiabile ovunque tale interfaccia sia attesa.
Implementare un'Interfaccia
Usa la parola chiave implements seguita dal nome dell'interfaccia, poi fornisci un corpo per ogni metodo dichiarato nell'interfaccia.
<?php
interface Logger {
public function log(string $message): void;
}
class ConsoleLogger implements Logger {
public function log(string $message): void {
echo "[LOG] {$message}\n";
}
}
$logger = new ConsoleLogger();
$logger->log('App started');
// Output: [LOG] App startedConsoleLogger rispetta il contratto Logger, quindi ovunque il codice si aspetti un Logger è possibile passare un ConsoleLogger.
Type Hinting con un'Interfaccia
Il vantaggio maggiore si ottiene quando si esegue il type-hint dell'interfaccia anziché di una classe concreta. La funzione seguente funziona con qualsiasi Logger, quindi è possibile cambiare implementazioni senza riscriverla.
<?php
interface Logger {
public function log(string $message): void;
}
class ConsoleLogger implements Logger {
public function log(string $message): void {
echo "[LOG] {$message}\n";
}
}
class SilentLogger implements Logger {
public function log(string $message): void {
// intentionally does nothing
}
}
function runJob(Logger $logger): void {
$logger->log('Job finished');
}
runJob(new ConsoleLogger()); // Output: [LOG] Job finished
runJob(new SilentLogger()); // Output: (nothing)Implementare Più Interfacce
Una classe può implementare più interfacce contemporaneamente separando i loro nomi con le virgole. Questo è il modo in cui PHP aggira la mancanza di ereditarietà multipla di classe.
<?php
interface Drawable {
public function draw(): string;
}
interface Serializable {
public function toArray(): array;
}
class Circle implements Drawable, Serializable {
public function __construct(private float $radius) {}
public function draw(): string {
return "Circle(r={$this->radius})";
}
public function toArray(): array {
return ['type' => 'circle', 'radius' => $this->radius];
}
}
$c = new Circle(2.5);
echo $c->draw() . "\n"; // Output: Circle(r=2.5)
echo json_encode($c->toArray()); // Output: {"type":"circle","radius":2.5}Costanti nelle Interfacce
Le interfacce possono contenere costanti, che ogni classe che le implementa eredita. Sono utili per valori condivisi e fissi che appartengono al contratto.
<?php
interface HttpStatus {
const OK = 200;
const NOT_FOUND = 404;
}
class Response implements HttpStatus {
public function notFound(): int {
return self::NOT_FOUND;
}
}
$r = new Response();
echo $r->notFound(); // Output: 404
echo HttpStatus::OK; // Output: 200Estendere le Interfacce
Un'interfaccia può costruire su un'altra con extends e, a differenza delle classi, un'interfaccia può estendere più interfacce contemporaneamente. Una classe che implementa l'interfaccia figlia deve soddisfare tutti i metodi ereditati.
<?php
interface Readable {
public function read(): string;
}
interface Writable {
public function write(string $data): void;
}
interface ReadWrite extends Readable, Writable {}
class Memory implements ReadWrite {
private string $buffer = '';
public function read(): string {
return $this->buffer;
}
public function write(string $data): void {
$this->buffer .= $data;
}
}
$m = new Memory();
$m->write('hello ');
$m->write('world');
echo $m->read(); // Output: hello worldVerificare un'Interfaccia
Usa l'operatore instanceof per confermare che un oggetto rispetti un determinato contratto prima di chiamare i suoi metodi.
<?php
interface Logger {
public function log(string $message): void;
}
class ConsoleLogger implements Logger {
public function log(string $message): void {
echo $message . "\n";
}
}
$obj = new ConsoleLogger();
var_dump($obj instanceof Logger); // Output: bool(true)Interfaccia vs. Classe Astratta
Si sovrappongono ma risolvono problemi diversi:
| Interfaccia | Classe astratta | |
|---|---|---|
| Corpi dei metodi | No (solo firme) | Sì (può mescolare concreti + astratti) |
| Proprietà | No (solo costanti) | Sì |
| Quante per classe | Molte (implements A, B) | Una (extends A) |
| Usala quando | Classi non correlate condividono una capacità | Classi correlate condividono codice e stato |
Scegli un'interfaccia per descrivere una capacità; scegli una classe astratta per condividere un'implementazione parziale tra classi correlate. Se hai bisogno di condividere corpi di metodi tra classi non correlate, consulta i Trait PHP.
Errori Comuni
- Metodo mancante. Dimenticare di implementare anche un solo metodo dell'interfaccia genera un errore fatale alla dichiarazione della classe.
- Visibilità ridotta. I metodi delle interfacce sono pubblici; non è possibile implementarli come
protectedoprivate. - Modifica della firma. I parametri e il tipo di ritorno del metodo implementato devono rimanere compatibili con l'interfaccia, altrimenti PHP genera un errore.
- Aspettarsi proprietà. Le interfacce dichiarano comportamenti, non dati — non possono contenere proprietà, solo costanti.
Conclusione
Le interfacce ti permettono di programmare verso un contratto anziché verso una classe concreta: definiscono cosa una classe deve fare, supportano implementazioni multiple e rendono il polimorfismo, i test e il refactoring molto più semplici. Combinale con l'ereditarietà e i trait per progettare applicazioni PHP flessibili e manutenibili.