Objektorientierte Programmierung mit PHP Interfaces
PHP Interfaces sind ein grundlegender Bestandteil der OOP: Verträge, die festlegen, welche Methoden eine Klasse bereitstellen muss.
Ein Interface ist ein Vertrag, der die Methoden auflistet, die eine Klasse bereitstellen muss, ohne anzugeben, wie diese Methoden funktionieren. Es ermöglicht nicht verwandten Klassen, sich auf eine gemeinsame Menge von Verhaltensweisen zu einigen, sodass der restliche Code vom Vertrag und nicht von einer bestimmten Klasse abhängen kann. Dieses Kapitel behandelt die Deklaration von Interfaces, ihre Implementierung, die Verwendung von Konstanten, das Erweitern und Kombinieren von Interfaces sowie häufige Fallstricke.
Wenn Sie noch neu bei Objekten sind, lesen Sie zuerst PHP Klassen und Objekte und Was ist OOP in PHP.
Was ist ein Interface in PHP?
Ein Interface definiert eine Menge öffentlicher Methoden-Signaturen — Namen, Parameter und (optional) Rückgabetypen — aber keine Methodenrümpfe. Jede Klasse, die das Interface implementiert, verspricht, für jede dieser Methoden einen konkreten Rumpf bereitzustellen. Tut sie es nicht, wirft PHP einen fatalen Fehler.
Da das Interface nur angibt, was eine Klasse tun kann und niemals wie, können Sie eine Implementierung gegen eine andere austauschen, ohne den Code zu ändern, der sie verwendet. Diese Entkopplung ist der eigentliche Zweck.
<?php
interface Logger {
public function log(string $message): void;
}Wichtige Regeln für Interface-Member:
- Alle Methoden sind implizit public und abstract — Sie können ihnen keine Rümpfe geben und können sie nicht als
privateoderprotectedmarkieren. - Ein Interface kann Konstanten deklarieren, aber keine Eigenschaften.
- Eine Klasse signalisiert mit dem Schlüsselwort
implements, dass sie den Vertrag erfüllt.
Warum Interfaces verwenden?
- Verträge. Sie garantieren, dass jede implementierende Klasse eine vereinbarte API bereitstellt, sodass Aufrufer genau wissen, welche Methoden verfügbar sind.
- Lose Kopplung. Code kann gegen das Interface (
Logger) statt gegen eine konkrete Klasse (FileLogger) geschrieben werden, sodass Implementierungen frei ausgetauscht werden können — ein echter Datei-Logger in der Produktion, ein gefälschter in Tests. - Mehrere Typen. Anders als bei der Klassenvererbung kann eine Klasse viele Interfaces implementieren und dadurch mehrere Rollen gleichzeitig einnehmen.
- Polymorphismus. Verschiedene Objekte, die ein Interface teilen, können überall dort austauschbar verwendet werden, wo dieses Interface erwartet wird.
Ein Interface implementieren
Verwenden Sie das Schlüsselwort implements gefolgt vom Interface-Namen und geben Sie dann für jede Methode, die das Interface deklariert, einen Rumpf an.
<?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 erfüllt den Logger-Vertrag, sodass Sie überall dort, wo Ihr Code einen Logger erwartet, einen ConsoleLogger übergeben können.
Type Hinting gegen ein Interface
Der größte Vorteil entsteht, wenn Sie das Interface als Type Hint verwenden statt einer konkreten Klasse. Die folgende Funktion funktioniert mit jedem Logger, sodass Implementierungen ohne Umschreiben ausgetauscht werden können.
<?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)Mehrere Interfaces implementieren
Eine Klasse kann mehrere Interfaces gleichzeitig implementieren, indem deren Namen durch Kommas getrennt werden. So umgeht PHP das Fehlen von Mehrfachklassenvererbung.
<?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}Interface-Konstanten
Interfaces können Konstanten enthalten, die jede implementierende Klasse erbt. Sie sind nützlich für gemeinsame, feste Werte, die zum Vertrag gehören.
<?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: 200Interfaces erweitern
Ein Interface kann mit extends auf einem anderen aufbauen, und anders als bei Klassen kann ein Interface mehrere Interfaces gleichzeitig erweitern. Eine Klasse, die das abgeleitete Interface implementiert, muss alle geerbten Methoden erfüllen.
<?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 worldAuf ein Interface prüfen
Verwenden Sie den instanceof-Operator, um zu bestätigen, dass ein Objekt einen bestimmten Vertrag erfüllt, bevor Sie seine Methoden aufrufen.
<?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)Interface vs. abstrakte Klasse
Sie überschneiden sich, lösen aber unterschiedliche Probleme:
| Interface | Abstrakte Klasse | |
|---|---|---|
| Methodenrümpfe | Nein (nur Signaturen) | Ja (kann konkrete + abstrakte mischen) |
| Eigenschaften | Nein (nur Konstanten) | Ja |
| Wie viele pro Klasse | Viele (implements A, B) | Eine (extends A) |
| Verwenden wenn | Nicht verwandte Klassen teilen eine Fähigkeit | Verwandte Klassen teilen Code und Zustand |
Greifen Sie auf ein Interface zurück, um eine Fähigkeit zu beschreiben; greifen Sie auf eine abstrakte Klasse zurück, um eine teilweise Implementierung unter verwandten Klassen zu teilen. Wenn Sie Methodenrümpfe über nicht verwandte Klassen hinweg teilen müssen, lesen Sie PHP Traits.
Häufige Fallstricke
- Eine Methode fehlt. Wenn auch nur eine Interface-Methode nicht implementiert wird, löst dies bei der Klassendeklaration einen fatalen Fehler aus.
- Sichtbarkeit einschränken. Interface-Methoden sind public; Sie können sie nicht als
protectedoderprivateimplementieren. - Die Signatur ändern. Die Parameter und der Rückgabetyp der implementierenden Methode müssen mit dem Interface kompatibel bleiben, sonst wirft PHP einen Fehler.
- Eigenschaften erwarten. Interfaces deklarieren Verhalten, keine Daten — sie können keine Eigenschaften enthalten, nur Konstanten.
Fazit
Interfaces ermöglichen es Ihnen, gegen einen Vertrag statt gegen eine konkrete Klasse zu programmieren: Sie definieren, was eine Klasse tun muss, unterstützen mehrere Implementierungen und erleichtern Polymorphismus, Tests und Refactoring erheblich. Kombinieren Sie sie mit Vererbung und Traits, um flexible, wartbare PHP-Anwendungen zu entwerfen.