DNF Types in PHP 8.2: Union und Intersection Types kombinieren
AI generated
8.4
PHP · DNF Types · PHP 8.2
DNF Types in PHP 8.2
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.

10 Min. Lesezeit Typsystem PHP 8.2+

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.

11. FAQ: DNF Types

1Was bedeutet DNF in DNF Types?
DNF steht für Disjunctive Normal Form, eine logische Normalform, bei der eine Oder-Verknüpfung aus mehreren Und-Gruppen besteht, genau wie bei der Kombination aus Union und Intersection Types.
2Seit welcher PHP Version gibt es DNF Types?
DNF Types wurden mit PHP 8.2 eingeführt, zusammen mit readonly Klassen und mehreren anderen Typsystem-Erweiterungen.
3Warum reichen reine Union Types manchmal nicht aus?
Union Types beschreiben nur ein Entweder-Oder zwischen einzelnen Typen, sie können nicht ausdrücken, dass ein Typ gleichzeitig mehrere Interfaces erfüllen muss.
4Sind Klammern bei DNF Types immer nötig?
Ja, jede Intersection-Gruppe innerhalb eines Union Types muss in runden Klammern stehen, ohne Klammern akzeptiert der Parser die Kombination nicht.
5Kann man DNF Types verschachteln?
Nein, verschachtelte Klammern wie eine Oder-Gruppe innerhalb einer Und-Verknüpfung sind nicht erlaubt, DNF Types beschreiben nur eine flache Ebene.
6Funktionieren DNF Types auch bei Properties?
Ja, DNF Types sind bei Parametern, Return Types und Properties gleichermaßen erlaubt, inklusive der Kombination mit readonly.
7Kann eine Intersection aus zwei konkreten Klassen bestehen?
Nur wenn eine Klasse von der anderen erbt oder beide dasselbe Interface implementieren, eine reine Kombination aus zwei unverwandten Klassen funktioniert nicht.
8Erkennen PHPStan und Psalm DNF Types korrekt?
Ja, beide Tools interpretieren die Und- und Oder-Kombination korrekt und bieten passende Autovervollständigung innerhalb eingegrenzter Union-Zweige.
9Was war die Alternative zu DNF Types vor PHP 8.2?
Meist ein zusätzliches Marker-Interface, das mehrere Interfaces künstlich zusammenfasst, oder ein Verzicht auf strikte Typisierung zugunsten eines Docblocks.
10Lohnen sich DNF Types für jedes Projekt?
Sie lohnen sich überall dort, wo Signaturen bislang auf künstliche Marker-Interfaces oder ungetypte Parameter zurückgreifen mussten, um kombinierte Fähigkeiten abzubilden.