Union und Intersection Types kombinieren
Reine Union Types beschreiben ein Entweder-Oder, reine Intersection Types ein Sowohl-als-auch, aber manche Signaturen brauchen beides gleichzeitig. Disjunctive Normal Form Types erlauben seit PHP 8.2 genau diese Kombination mit einer klaren, aber strikt reglementierten Klammersyntax.
Inhaltsverzeichnis
- 1. Wo reine Union und Intersection Types an Grenzen stoßen
- 2. Syntax der DNF Types: Klammerpflicht und Parserregeln
- 3. Praxisbeispiel: nullable Intersection Types
- 4. Praxisbeispiel: Event Listener mit mehreren Fähigkeiten
- 5. Was mit DNF Types nicht erlaubt ist
- 6. DNF Types in Properties und Return Types
- 7. DNF Types in der statischen Analyse
- 8. Praxisfall: Decorator mit mehreren Capability-Interfaces
- 9. Alternativen vor PHP 8.2 und warum DNF die bessere Lösung ist
- 10. Zusammenfassung
- 11. FAQ
1. Wo reine Union und Intersection Types an Grenzen stoßen
Union Types und Intersection Types sind eigene Themen mit eigenen Grundregeln, hier geht es nicht um deren Grundlagen, sondern um den Fall, in dem beide gleichzeitig gebraucht werden. Ein Parameter kann entweder ein einfacher Typ sein, oder er muss zwei Interfaces gleichzeitig erfüllen: Diese Kombination lässt sich mit reinem Union oder reiner Intersection allein nicht ausdrücken.
Vor PHP 8.2 blieb in solchen Fällen nur der Ausweg über ein künstliches Marker-Interface, das beide ursprünglichen Interfaces erweitert, oder der Verzicht auf Typisierung zugunsten eines dokumentierten, aber ungeprüften Docblocks. Beide Auswege kosten entweder zusätzliche Klassenhierarchie oder Typsicherheit.
Die Bezeichnung Disjunctive Normal Form stammt aus der Aussagenlogik und beschreibt eine Formel als Oder-Verknüpfung mehrerer Und-Gruppen. Genau dieses Muster überträgt PHP 8.2 auf das Typsystem: Jede Und-Gruppe ist eine Intersection, jede Verknüpfung per Oder ist eine Union, und beide zusammen ergeben einen DNF Type mit einer festen, vorhersagbaren Struktur.
2. Syntax der DNF Types: Klammerpflicht und Parserregeln
Die Syntax folgt dem Muster (A&B)|C: Eine Intersection aus mehreren Typen wird in runden Klammern zusammengefasst und per Union mit weiteren Typen verbunden. Die Klammern sind dabei nicht optional, sie sind für jede Intersection innerhalb eines Union Types zwingend vorgeschrieben, sobald mehr als ein Typ auf der Union-Seite steht.
Der Parser erlaubt beliebig viele solcher geklammerten Intersection-Gruppen innerhalb eines Union Types, aber keine Verschachtelung von Klammern ineinander. Eine Intersection kann also nicht selbst wieder eine geklammerte Union enthalten, die Disjunctive Normal Form beschreibt bewusst nur eine flache Ebene aus Und-Gruppen, die per Oder verbunden werden.
function process((Countable&Iterator)|null $items): void
{
if ($items === null) {
return;
}
foreach ($items as $item) {
// $items is guaranteed to be both Countable and Iterator here
}
}
3. Praxisbeispiel: nullable Intersection Types
Ein häufiger Anwendungsfall kombiniert eine Intersection aus mehreren Interfaces mit null als drittem, alternativem Typ, etwa wenn ein optionaler Parameter entweder ein Objekt mit mehreren erfüllten Fähigkeiten oder eben kein Wert sein darf. Genau das zeigt das vorherige Beispiel mit (Countable&Iterator)|null.
Ohne DNF Types hätte man entweder auf die Typisierung des null-Falls verzichten oder ein zusätzliches Interface CountableIterator einführen müssen, das beide Fähigkeiten künstlich zusammenfasst, nur um eine einzelne Parametersignatur abzubilden. DNF Types machen dieses Interface überflüssig.
4. Praxisbeispiel: Event Listener mit mehreren Fähigkeiten
Ein Event-System, das sowohl einfache Listener als auch Listener mit Priorisierungslogik akzeptiert, profitiert von DNF Types besonders deutlich. Ein Parameter kann dann entweder ein einfaches ListenerInterface sein, oder ein Objekt, das gleichzeitig PrioritizedInterface und ListenerInterface implementiert, ohne dass eine dritte Klassenhierarchie nötig wird.
Diese Art von Signatur kommt in Middleware-Stacks und Plugin-Systemen häufig vor, wenn optionale Zusatzfähigkeiten wie Priorisierung, Logging oder Konfigurierbarkeit unabhängig voneinander kombinierbar sein sollen, aber nicht jede Kombination als eigenes benanntes Interface existieren soll.
interface ListenerInterface
{
public function handle(object $event): void;
}
interface PrioritizedInterface
{
public function getPriority(): int;
}
function registerListener(ListenerInterface|(ListenerInterface&PrioritizedInterface) $listener): void
{
// Both a plain listener and a prioritized listener are accepted here
}
5. Was mit DNF Types nicht erlaubt ist
Verschachtelte Klammern sind der häufigste Stolperstein: Ein Ausdruck wie ((A&B)|C)&D wird vom Parser abgelehnt, weil DNF Types nur eine flache Struktur aus geklammerten Und-Gruppen erlauben, die per Oder verbunden sind, nicht umgekehrt eine Oder-Gruppe innerhalb einer Und-Verknüpfung.
Ebenfalls nicht erlaubt ist eine Intersection aus zwei konkreten, nicht verwandten Klassen ohne gemeinsames Interface, denn eine Klasse kann in PHP grundsätzlich nur von einer einzigen anderen Klasse erben. Intersection Types funktionieren daher nur zuverlässig mit Interfaces oder einer Kombination aus genau einer konkreten Klasse mit mehreren zusätzlichen Interfaces.
// Parse error: nested parentheses are not allowed in DNF types
function invalid(((Countable&Iterator)|ArrayAccess)&Stringable $value): void
{
}
6. DNF Types in Properties und Return Types
DNF Types sind nicht auf Parameter beschränkt, sie funktionieren identisch in Property-Deklarationen und Return Types. Eine Methode kann also (JsonSerializable&Countable)|string zurückgeben, wenn sie je nach internem Zustand entweder ein serialisierbares, zählbares Objekt oder einen bereits fertig gerenderten String liefert.
Bei Properties gilt dieselbe Klammerpflicht wie bei Parametern, und die Kombination mit readonly ist ebenfalls erlaubt: Eine readonly Property kann also einen DNF Type tragen, ohne dass sich die Regeln für Unveränderlichkeit oder für die Typsyntax gegenseitig einschränken.
Auch bei Konstruktor-Parametern über Constructor Property Promotion lässt sich ein DNF Type direkt angeben, ohne dass eine separate Property-Deklaration nötig wäre. Das reduziert Boilerplate besonders bei Value Objects, die von vornherein mehrere alternative, jeweils aus mehreren Interfaces zusammengesetzte Eingabeformen akzeptieren sollen.
7. DNF Types in der statischen Analyse
PHPStan und Psalm interpretieren DNF Types korrekt als Kombination aus Und- und Oder-Bedingungen und geben in IDEs präzise Autovervollständigung für jede Kombination aus, sodass innerhalb eines if-Blocks, der auf einen der Union-Zweige eingrenzt, automatisch alle Methoden der jeweiligen Intersection sichtbar werden.
Für die Codequalität bedeutet das einen echten Gewinn gegenüber dem alten Marker-Interface-Ansatz: Die Analyse-Tools müssen keine zusätzliche, künstliche Klassenhierarchie mehr verstehen, sondern lesen die tatsächlich benötigte Typkombination direkt aus der Signatur. Auch Editoren wie PhpStorm profitieren direkt davon, ohne dass ein eigenes Plugin oder eine Sonderbehandlung für DNF Types nötig wäre.
8. Praxisfall: Decorator mit mehreren Capability-Interfaces
In einem Decorator-Pattern, das mehrere unabhängige Zusatzfähigkeiten wie Caching, Logging und Retry-Logik kombiniert, lassen sich DNF Types nutzen, um einer Factory-Funktion genau zu sagen, welche Kombinationen an Fähigkeiten akzeptiert werden, ohne für jede Kombination ein eigenes benanntes Interface zu pflegen.
Das reduziert die Anzahl der Interfaces im Projekt spürbar, weil nicht mehr jede denkbare Kombination aus Fähigkeiten vorab als eigener Typ modelliert werden muss. Stattdessen entstehen die Kombinationen erst dort, wo sie tatsächlich in einer Signatur gebraucht werden.
Gerade in wachsenden Codebasen mit vielen kleinen, orthogonalen Capability-Interfaces verhindert dieser Ansatz eine kombinatorische Explosion an Interface-Namen, die sonst schnell unübersichtlich würde, sobald jede neue Kombination aus zwei oder drei Fähigkeiten einen eigenen, oft schwer benennbaren Interfacenamen bräuchte.
9. Alternativen vor PHP 8.2 und warum DNF die bessere Lösung ist
Vor PHP 8.2 blieben im Wesentlichen zwei Alternativen: ein zusätzliches, künstliches Marker-Interface, das die benötigte Kombination fest verdrahtet, oder ein Verzicht auf strikte Typisierung zugunsten eines Docblock-Kommentars, der von keinem Tool zur Laufzeit erzwungen wird.
Beide Alternativen haben einen echten Preis: Das Marker-Interface bläht die Klassenhierarchie auf und muss bei jeder neuen Kombination erweitert werden, der Docblock-Ansatz verliert die Garantie des Typsystems komplett. DNF Types lösen beide Probleme, indem die Kombination direkt und ad hoc in der Signatur steht, ohne zusätzliche Klasse und ohne Verzicht auf echte Typprüfung. Für neue Projekte lohnt sich deshalb ein bewusster Blick auf jede Stelle, an der bislang ein Marker-Interface allein aus Typgründen existiert.
| Merkmal | Union Type | Intersection Type | DNF Type |
|---|---|---|---|
| Bedeutung | Entweder-Oder zwischen Typen | Sowohl-als-auch gleichzeitig | Kombination aus beidem |
| Klammerpflicht | Keine Klammern nötig | Keine Klammern nötig | Klammern um jede Intersection-Gruppe zwingend |
| Seit PHP Version | PHP 8.0 | PHP 8.1 | PHP 8.2 |
| Verschachtelung | Nicht relevant | Nicht relevant | Nur eine flache Ebene erlaubt |
| Typischer Einsatz | Ein Parameter akzeptiert mehrere einfache Typen | Ein Parameter muss mehrere Interfaces erfüllen | Ein Parameter braucht Interface-Kombination plus Alternative |
| Beispiel-Signatur | int|string $id |
Countable&Iterator $items |
(Countable&Iterator)|null $items |
Mironsoft
PHP-Modernisierung, Code-Qualität und Legacy-Refactoring
Gewachsener PHP-Code, der niemand mehr gern anfasst?
Wir modernisieren PHP-Codebasen auf aktuelle Sprachstandards, führen statische Analyse und Coding Standards ein und refactorn Legacy-Code Schritt für Schritt, ohne den laufenden Betrieb zu gefährden.
Legacy-Refactoring
Gewachsenen PHP-Code strukturiert und risikoarm modernisieren.
Code-Qualität etablieren
PHPStan, Coding Standards und CI-Checks nachhaltig im Team verankern.
Versions-Upgrade
PHP-Major-Version-Upgrades sicher planen und ohne Ausfallzeit umsetzen.
10. Zusammenfassung
DNF Types
Kernidee
DNF Types kombinieren Union und Intersection in einem Typ, mit Pflichtklammern um jede Und-Gruppe.
Syntax
Das Muster (A&B)|C erlaubt beliebig viele geklammerte Intersection-Gruppen, aber keine Verschachtelung.
Vorteil
Kein künstliches Marker-Interface mehr nötig, um kombinierte Fähigkeiten in einer Signatur abzubilden.
Grenze
Intersection Types funktionieren zuverlässig nur mit Interfaces, nicht mit zwei unverwandten Klassen.