13 Interfaces und abstrakte Klassen

Interfaces und abstrakte Klassen sind zwei fundamentale Konzepte in der objektorientierten Programmierung, die in PHP vollständig unterstützt werden. Beide bieten Mechanismen zur Definition von Vertragsbedingungen für Klassen, unterscheiden sich jedoch in ihrer Anwendung und ihren Möglichkeiten.

13.1 Abstrakte Klassen

Eine abstrakte Klasse dient als Basis für andere Klassen, kann aber selbst nicht instanziiert werden. Sie wird mit dem Schlüsselwort abstract definiert und kann sowohl abstrakte als auch konkrete Methoden und Eigenschaften enthalten.

13.1.1 Definition einer abstrakten Klasse

abstract class Dokument {
    protected string $titel;
    protected string $autor;
    protected DateTime $erstelltAm;
    
    public function __construct(string $titel, string $autor) {
        $this->titel = $titel;
        $this->autor = $autor;
        $this->erstelltAm = new DateTime();
    }
    
    // Konkrete Methode
    public function getMetadaten(): string {
        return "Titel: {$this->titel}, Autor: {$this->autor}, Erstellt: {$this->erstelltAm->format('d.m.Y')}";
    }
    
    // Abstrakte Methode - muss von Kindklassen implementiert werden
    abstract public function anzeigen(): string;
    
    // Abstrakte Methode mit Parametern
    abstract public function exportieren(string $format): string;
}

13.1.2 Verwendung einer abstrakten Klasse

Abstrakte Klassen müssen von konkreten Klassen erweitert werden, die alle abstrakten Methoden implementieren:

class PDFDokument extends Dokument {
    private string $pfad;
    
    public function __construct(string $titel, string $autor, string $pfad) {
        parent::__construct($titel, $autor);
        $this->pfad = $pfad;
    }
    
    public function anzeigen(): string {
        return "PDF wird angezeigt: {$this->titel} ({$this->pfad})";
    }
    
    public function exportieren(string $format): string {
        return "PDF wird nach {$format} exportiert...";
    }
}

class TextDokument extends Dokument {
    private string $inhalt;
    
    public function __construct(string $titel, string $autor, string $inhalt) {
        parent::__construct($titel, $autor);
        $this->inhalt = $inhalt;
    }
    
    public function anzeigen(): string {
        return "Textdokument: {$this->titel}\n{$this->inhalt}";
    }
    
    public function exportieren(string $format): string {
        switch ($format) {
            case 'html':
                return "<html><body><h1>{$this->titel}</h1><p>{$this->inhalt}</p></body></html>";
            case 'json':
                return json_encode([
                    'titel' => $this->titel,
                    'autor' => $this->autor,
                    'inhalt' => $this->inhalt
                ]);
            default:
                return $this->inhalt;
        }
    }
}

13.1.3 Schlüsselmerkmale abstrakter Klassen

  1. Eine abstrakte Klasse kann nicht direkt instanziiert werden:

    $dokument = new Dokument("Titel", "Autor"); // Fehler!
  2. Abstrakte Methoden haben keine Implementierung in der abstrakten Klasse.

  3. Jede Klasse, die eine abstrakte Klasse erweitert, muss alle abstrakten Methoden implementieren oder selbst abstrakt sein.

  4. Abstrakte Klassen können konkrete Methoden und Eigenschaften enthalten, die von Kindklassen geerbt werden.

  5. Abstrakte Klassen können protected und private Eigenschaften und Methoden haben, was bei Interfaces nicht möglich ist.

13.2 Interfaces

Ein Interface definiert einen Vertrag, den implementierende Klassen einhalten müssen. Es spezifiziert, welche Methoden eine Klasse haben muss, ohne deren Implementierung vorzugeben.

13.2.1 Definition eines Interfaces

interface Druckbar {
    public function drucken(): string;
    public function seitenanzahlBerechnen(): int;
}

interface Exportierbar {
    public function alsPDF(): string;
    public function alsWord(): string;
    public function alsPlaintext(): string;
}

13.2.2 Implementierung von Interfaces

Eine Klasse kann ein oder mehrere Interfaces implementieren:

class Bericht implements Druckbar, Exportierbar {
    private string $titel;
    private array $abschnitte;
    
    public function __construct(string $titel, array $abschnitte) {
        $this->titel = $titel;
        $this->abschnitte = $abschnitte;
    }
    
    public function drucken(): string {
        return "Drucke Bericht: {$this->titel}";
    }
    
    public function seitenanzahlBerechnen(): int {
        // Einfache Berechnung: Ein Abschnitt pro Seite
        return count($this->abschnitte);
    }
    
    public function alsPDF(): string {
        return "Exportiere als PDF: {$this->titel}";
    }
    
    public function alsWord(): string {
        return "Exportiere als Word: {$this->titel}";
    }
    
    public function alsPlaintext(): string {
        return "Titel: {$this->titel}\n" . implode("\n\n", $this->abschnitte);
    }
}

13.2.3 Schlüsselmerkmale von Interfaces

  1. Alle Methoden in einem Interface sind implizit abstrakt und public.

  2. Interfaces können keine Implementierungen enthalten (vor PHP 8.0).

  3. Eine Klasse kann mehrere Interfaces implementieren, aber nur von einer Klasse erben.

  4. Interfaces können andere Interfaces erweitern.

  5. Interfaces können Konstanten definieren, aber keine Eigenschaften (Variablen).

13.3 Interface-Konstanten

Interfaces können Konstanten definieren, die von implementierenden Klassen verwendet werden können:

interface Zahlungsstatus {
    const AUSSTEHEND = 'ausstehend';
    const VERARBEITET = 'verarbeitet';
    const STORNIERT = 'storniert';
    const FEHLGESCHLAGEN = 'fehlgeschlagen';
}

class Bestellung implements Zahlungsstatus {
    private string $status;
    
    public function __construct() {
        $this->status = self::AUSSTEHEND;
    }
    
    public function verarbeiteZahlung(): void {
        // Zahlungslogik...
        $this->status = self::VERARBEITET;
    }
    
    public function getStatus(): string {
        return $this->status;
    }
}

$bestellung = new Bestellung();
echo $bestellung->getStatus(); // Ausgabe: ausstehend
$bestellung->verarbeiteZahlung();
echo $bestellung->getStatus(); // Ausgabe: verarbeitet

// Direkter Zugriff auf Interface-Konstanten
echo Zahlungsstatus::STORNIERT; // Ausgabe: storniert

13.4 Interface-Vererbung

Interfaces können andere Interfaces erweitern:

interface Basis {
    public function methodA(): void;
}

interface Erweitert extends Basis {
    public function methodB(): void;
}

// Diese Klasse muss sowohl methodA als auch methodB implementieren
class KonkreteKlasse implements Erweitert {
    public function methodA(): void {
        echo "Methode A";
    }
    
    public function methodB(): void {
        echo "Methode B";
    }
}

13.5 Typ-Deklarationen mit Interfaces

Interfaces sind besonders nützlich für Typ-Deklarationen und ermöglichen polymorphes Verhalten:

function druckeElement(Druckbar $element): void {
    echo $element->drucken();
    echo "Anzahl der Seiten: " . $element->seitenanzahlBerechnen();
}

// Kann mit jeder Klasse verwendet werden, die Druckbar implementiert
$bericht = new Bericht("Quartalsbericht", ["Einleitung", "Finanzen", "Ausblick"]);
druckeElement($bericht);

13.6 Neue Interface-Features in PHP 8

Ab PHP 8.0 unterstützen Interfaces zusätzliche Features:

13.6.1 Union Types in Interface-Methoden

interface Konvertierbar {
    public function konvertieren(string|array $daten): string|array;
}

13.6.2 Interface-Attribute

#[Attribute]
interface ValidierungsRegel {
    public function validieren($wert): bool;
}

#[ValidierungsRegel]
class EmailValidierung implements ValidierungsRegel {
    public function validieren($wert): bool {
        return filter_var($wert, FILTER_VALIDATE_EMAIL) !== false;
    }
}

13.7 Abstrakte Klassen vs. Interfaces

Abstrakte Klassen Interfaces
Instanziierung Kann nicht instanziiert werden Kann nicht instanziiert werden
Methoden Konkrete und abstrakte Methoden Nur abstrakte Methoden (bis PHP 8.0)
Eigenschaften Kann Eigenschaften haben Keine Eigenschaften, nur Konstanten
Vererbung Einfachvererbung Mehrfachimplementierung möglich
Zugriffsmodifikatoren public, protected, private Nur public
Verwendungszweck Basisimplementierung und Vertragserzwingung Vertragsdefinition

13.8 Wann abstrakte Klassen, wann Interfaces?

13.9 Praktisches Beispiel: Content-Management-System

Betrachten wir ein praktisches Beispiel für ein einfaches Content-Management-System, das sowohl abstrakte Klassen als auch Interfaces verwendet:

// Interface für alle Entitäten, die in einer Datenbank gespeichert werden können
interface Persistierbar {
    public function speichern(): bool;
    public function laden(int $id): bool;
    public function löschen(): bool;
}

// Interface für Elemente, die im Frontend angezeigt werden können
interface Darstellbar {
    public function alsHTML(): string;
    public function alsText(): string;
}

// Abstrakte Basisklasse für alle Content-Typen
abstract class Content implements Persistierbar {
    protected ?int $id = null;
    protected string $titel;
    protected string $autor;
    protected DateTime $erstelltAm;
    protected DateTime $aktualisiertAm;
    
    public function __construct(string $titel, string $autor) {
        $this->titel = $titel;
        $this->autor = $autor;
        $this->erstelltAm = new DateTime();
        $this->aktualisiertAm = new DateTime();
    }
    
    // Gemeinsame Implementierung für alle Content-Typen
    public function speichern(): bool {
        // Speichert in der Datenbank, setzt ID wenn erfolgreich
        $this->aktualisiertAm = new DateTime();
        echo "Content '{$this->titel}' wurde gespeichert.\n";
        $this->id = $this->id ?? random_int(1, 1000); // Simuliert Datenbankzuweisung
        return true;
    }
    
    public function laden(int $id): bool {
        // Lädt aus der Datenbank
        $this->id = $id;
        echo "Content mit ID {$id} wurde geladen.\n";
        return true;
    }
    
    public function löschen(): bool {
        // Löscht aus der Datenbank
        echo "Content '{$this->titel}' wurde gelöscht.\n";
        $this->id = null;
        return true;
    }
    
    // Abstrakte Methoden, die von allen Content-Typen implementiert werden müssen
    abstract public function getContentTyp(): string;
    abstract public function validieren(): bool;
}

// Konkrete Content-Typen
class Artikel extends Content implements Darstellbar {
    private string $inhalt;
    private array $tags;
    
    public function __construct(string $titel, string $autor, string $inhalt, array $tags = []) {
        parent::__construct($titel, $autor);
        $this->inhalt = $inhalt;
        $this->tags = $tags;
    }
    
    public function getContentTyp(): string {
        return 'Artikel';
    }
    
    public function validieren(): bool {
        return strlen($this->titel) > 0 && strlen($this->inhalt) > 50;
    }
    
    public function alsHTML(): string {
        $tagStr = implode(', ', $this->tags);
        return "<article>
                    <h1>{$this->titel}</h1>
                    <p class='metadata'>Von {$this->autor} am {$this->erstelltAm->format('d.m.Y')}</p>
                    <div class='content'>{$this->inhalt}</div>
                    <div class='tags'>Tags: {$tagStr}</div>
                </article>";
    }
    
    public function alsText(): string {
        $tagStr = implode(', ', $this->tags);
        return "{$this->titel}\n" .
               "Von {$this->autor} am {$this->erstelltAm->format('d.m.Y')}\n\n" .
               "{$this->inhalt}\n\n" .
               "Tags: {$tagStr}";
    }
}

class Video extends Content implements Darstellbar {
    private string $url;
    private int $dauer; // in Sekunden
    private string $beschreibung;
    
    public function __construct(string $titel, string $autor, string $url, int $dauer, string $beschreibung = '') {
        parent::__construct($titel, $autor);
        $this->url = $url;
        $this->dauer = $dauer;
        $this->beschreibung = $beschreibung;
    }
    
    public function getContentTyp(): string {
        return 'Video';
    }
    
    public function validieren(): bool {
        return filter_var($this->url, FILTER_VALIDATE_URL) !== false && $this->dauer > 0;
    }
    
    public function alsHTML(): string {
        $dauerFormatiert = sprintf('%02d:%02d', floor($this->dauer / 60), $this->dauer % 60);
        return "<div class='video'>
                    <h2>{$this->titel}</h2>
                    <p>Dauer: {$dauerFormatiert}</p>
                    <iframe src='{$this->url}' width='560' height='315' frameborder='0'></iframe>
                    <p>{$this->beschreibung}</p>
                </div>";
    }
    
    public function alsText(): string {
        $dauerFormatiert = sprintf('%02d:%02d', floor($this->dauer / 60), $this->dauer % 60);
        return "{$this->titel} [VIDEO - {$dauerFormatiert}]\n" .
               "URL: {$this->url}\n" .
               "{$this->beschreibung}";
    }
}

// Verwendung des CMS
$artikel = new Artikel(
    "PHP 8 Features",
    "Max Mustermann",
    "PHP 8 bringt viele neue Features wie Union Types, Attribute und mehr...",
    ["PHP", "Programmierung", "Web-Entwicklung"]
);

$video = new Video(
    "PHP-Tutorial: OOP Grundlagen",
    "Erika Musterfrau",
    "https://example.com/videos/php-oop-tutorial",
    1250, // 20:50 Minuten
    "In diesem Video lernen Sie die Grundlagen der objektorientierten Programmierung in PHP."
);

// Content-Management-Funktionen
function veröffentlicheContent(Content $content): void {
    if ($content->validieren()) {
        $content->speichern();
        echo "{$content->getContentTyp()} wurde veröffentlicht!\n";
    } else {
        echo "Validierungsfehler: {$content->getContentTyp()} kann nicht veröffentlicht werden.\n";
    }
}

function renderContent(Darstellbar $content, string $format = 'html'): string {
    return $format === 'html' ? $content->alsHTML() : $content->alsText();
}

// Demo
veröffentlicheContent($artikel);
veröffentlicheContent($video);

echo "\nArtikel als HTML:\n";
echo renderContent($artikel);

echo "\nVideo als Text:\n";
echo renderContent($video, 'text');

In diesem Beispiel:

  1. Persistierbar und Darstellbar sind Interfaces, die verschiedene Fähigkeiten definieren
  2. Content ist eine abstrakte Klasse, die grundlegende Implementierungen bereitstellt und weitere Anforderungen definiert
  3. Artikel und Video sind konkrete Klassen, die sowohl von Content erben als auch Darstellbar implementieren
  4. Die Funktionen veröffentlicheContent und renderContent nutzen Polymorphismus durch Typ-Deklarationen