Vererbung
<?php
abstract class Tier
{
public function __construct(protected string $name) {}
abstract public function laut(): string;
public function vorstellen(): string { return "{$this->name} sagt {$this->laut()}"; }
}
class Hund extends Tier
{
public function laut(): string { return "Wuff"; }
}
class Katze extends Tier
{
public function __construct(string $name, private int $leben = 9)
{
parent::__construct($name);
}
public function laut(): string { return "Miau"; }
public function vorstellen(): string
{
return parent::vorstellen() . " ({$this->leben} Leben)";
}
}
foreach ([new Hund("Rex"), new Katze("Mimi")] as $t) echo $t->vorstellen(), "\n";
var_dump(new Hund("x") instanceof Tier, get_parent_class(new Hund("x")));Ausgabe
Rex sagt Wuff Mimi sagt Miau (9 Leben) bool(true) string(4) "Tier"
extendserbt;parent::ruft die Elternversion aufabstractKlassen sind nicht instanziierbar, abstrakte Methoden müssen überschrieben werdenfinalverbietet Vererbung bzw. Überschreiben- Bevorzuge Komposition vor tiefer Vererbung
Interfaces
Ein Interface legt Methoden fest, die implementierende Klassen anbieten müssen:
<?php
interface Formatierbar
{
public function format(): string;
}
interface Vergleichbar
{
public function vergleiche(self $anderer): int;
}
class Preis implements Formatierbar, Vergleichbar
{
public function __construct(private float $betrag) {}
public function format(): string { return number_format($this->betrag, 2, ",", ".") . " €"; }
public function vergleiche(Vergleichbar $anderer): int { return $this->betrag <=> $anderer->betrag; }
}
function drucke(Formatierbar ...$dinge): void { foreach ($dinge as $d) echo $d->format(), "\n"; }
drucke(new Preis(1234.5), new Preis(9.99));
var_dump((new Preis(5))->vergleiche(new Preis(7)));Ausgabe
1.234,50 € 9,99 € int(-1)
Eine Klasse kann mehrere Interfaces implementieren, aber nur eine Elternklasse erben. Eingebaute Interfaces: Countable, ArrayAccess, IteratorAggregate, JsonSerializable, Stringable.
<?php
class Liste implements Countable, IteratorAggregate, ArrayAccess, JsonSerializable
{
private array $d = [];
public function count(): int { return count($this->d); }
public function getIterator(): Iterator { return new ArrayIterator($this->d); }
public function offsetExists(mixed $o): bool { return isset($this->d[$o]); }
public function offsetGet(mixed $o): mixed { return $this->d[$o] ?? null; }
public function offsetSet(mixed $o, mixed $v): void { if ($o === null) $this->d[] = $v; else $this->d[$o] = $v; }
public function offsetUnset(mixed $o): void { unset($this->d[$o]); }
public function jsonSerialize(): mixed { return ["items" => $this->d]; }
}
$l = new Liste();
$l[] = "a"; $l[] = "b"; $l["x"] = "c";
echo count($l), " ", $l[0], " ", isset($l["x"]) ? "ja" : "nein", " ", json_encode($l), "\n";
foreach ($l as $k => $v) echo "$k=$v ";
echo "\n";Ausgabe
3 a ja {"items":{"0":"a","1":"b","x":"c"}}
0=a 1=b x=cTraits
Traits teilen Code zwischen Klassen ohne Vererbung ("horizontale Wiederverwendung"):
<?php
trait Zeitstempel
{
private ?string $erstellt = null;
public function markiere(string $zeit): static { $this->erstellt = $zeit; return $this; }
public function erstelltAm(): ?string { return $this->erstellt; }
}
trait Logger
{
public function log(string $m): void { echo "[", static::class, "] $m\n"; }
}
class Artikel { use Zeitstempel, Logger; }
class Kommentar { use Zeitstempel; }
$a = (new Artikel())->markiere("2025-01-01");
$a->log("angelegt " . $a->erstelltAm());
echo (new Kommentar())->erstelltAm() ?? "kein Datum", "\n";Ausgabe
[Artikel] angelegt 2025-01-01 kein Datum
Abstrakt, Interface oder Trait?
| Ziel | Mittel |
|---|---|
| "Ist ein" mit gemeinsamem Code | abstrakte Klasse / Vererbung |
| Fähigkeit zusichern, Typ prüfen | Interface |
| Code teilen ohne "ist ein" | Trait |
Namespaces und Autoloading
Mit namespace vermeidest du Namenskonflikte; use importiert Klassen. Composer lädt Klassen automatisch (PSR-4: Namespace = Verzeichnispfad). Mehr dazu im Kapitel über Composer.
<?php
namespace App\Modell;
class Nutzer {}
echo Nutzer::class, " ", \strlen("abc"), "\n";Ausgabe
App\Modell\Nutzer 3
Merke
extends,parent::,abstract,final- Interfaces garantieren Methoden; mehrere sind möglich
- Traits teilen Code ohne Vererbung
- Eingebaute Interfaces (
Countable,ArrayAccess,IteratorAggregate,JsonSerializable) machen Objekte "native" - Namespaces + Composer-Autoloading organisieren Klassen
Aufgabe
Definiere ein Interface Form mit flaeche(), zwei Klassen und eine Funktion, die die Gesamtfläche einer Liste berechnet.