Einleitung: Warum PHP-Entwickler von Generics träumen
\n\nWenn Sie jemals in Java, C# oder TypeScript geschrieben haben und dann zu PHP zurückgekehrt sind, haben Sie sicherlich eine leichte Enttäuschung erlebt. In diesen Sprachen gibt es ein elegantes System generischer Typen (Generics), das die Erstellung flexibler, aber dennoch typsicherer Datenstrukturen ermöglicht. In PHP herrschte lange Zeit eine "Typisierungs-Anarchie": Arrays konnten alles enthalten, und die IDE markierte nur die offensichtlichsten Fehler.
\n\nAber die Zeiten ändern sich. Beginnend mit PHP 7.0 und insbesondere in der Ära von PHP 8.0/8.1/8.2 hat die Gemeinschaft einen Weg gefunden, Generics in PHP zu nutzen. Ja, auf Syntaxebene der Sprache gibt es sie noch nicht (und wird es wahrscheinlich in den nächsten Jahren auch nicht geben), aber das bedeutet nicht, dass Sie nicht schon heute alle Vorteile der generischen Programmierung nutzen können.
\n\nIn diesem Artikel werden wir untersuchen, wie die Typisierung in PHP mit Generics funktioniert, welche Werkzeuge dafür existieren (PHPStan, Psalm) und wie man Code schreibt, der gleichzeitig flexibel und sicher ist. Schnallen Sie sich an – es wird heiß!
\n\nWas sind Generics und warum werden sie in PHP benötigt?
\n\nGenerics (generische Typen) sind ein Mechanismus, der es ermöglicht, Klassen, Schnittstellen und Funktionen mit Typen zu parametrisieren. Anstatt eine separate Implementierung für jeden Datentyp zu schreiben, erstellen Sie eine universelle und setzen den konkreten Typ bei der Verwendung ein.
\n\nStellen Sie sich vor, Sie schreiben eine Klasse für eine Warteschlange (Queue). Ohne Generics können Sie darin nur einen Datentyp speichern oder mixed verwenden (was die gesamte Typisierung zunichte macht). Mit Generics können Sie sagen: "Dies ist eine Warteschlange von ganzen Zahlen" oder "Dies ist eine Warteschlange von User-Objekten".
Das Problem der "rohen" Typisierung
\n\nSchauen Sie sich diesen typischen PHP-Code an:
\n\n<?php\n\nclass Collection {\n private array $items = [];\n\n public function add($item): void {\n $this->items[] = $item;\n }\n\n public function get(int $index) {\n return $this->items[$index] ?? null;\n }\n}\n\n$users = new Collection();\n$users->add(new User('Alice'));\n$users->add(new User('Bob'));\n$users->add('das ist ein String, kein Benutzer'); // Fehler! Aber PHP schweigt\n\n$user = $users->get(2); // Gibt einen String zurück, IDE ahnt nichts\n$user->getName(); // Schwerwiegender Fehler zur Laufzeit\n\n\nDies ist ein klassisches Problem von PHP-Entwicklern: Fehlende Typprüfung zum Zeitpunkt der Code-Erstellung. Sie erfahren von dem Fehler erst zur Laufzeit, wenn der Benutzer bereits auf den Button geklickt hat. Generics lösen dieses Problem an der Wurzel.
\n\nGenerics in PHP: Wie es in der Praxis funktioniert
\n\nDa die native Syntax für Generics in PHP fehlt, verwenden wir Annotationen in PHPDoc, die von statischen Analysewerkzeugen – PHPStan und Psalm – verarbeitet werden. Diese Werkzeuge prüfen den Code auf Übereinstimmung mit den deklarierten Typen und emulieren so faktisch die Arbeit von Generics.
\n\nBasissyntax: Vorlagentypen
\n\nAngenommen, wir möchten unsere Sammlung typsicher machen. So sieht das mit Generics über PHPDoc aus:
\n\n<?php\n\n/**\n * @template T\n */\nclass GenericCollection {\n /** @var array<int, T> */\n private array $items = [];\n\n /**\n * @param T $item\n */\n public function add($item): void {\n $this->items[] = $item;\n }\n\n /**\n * @param int $index\n * @return T|null\n */\n public function get(int $index) {\n return $this->items[$index] ?? null;\n }\n}\n\n// Jetzt verwenden wir:\n/** @var GenericCollection<User> $users */\n$users = new GenericCollection();\n$users->add(new User('Alice'));\n$users->add('String'); // PHPStan gibt einen Fehler aus: Erwarteter Typ User, erhalten string\n\n$user = $users->get(0); // PHPStan weiß, dass es User ist\necho $user->getName(); //