Mit PHP 8.0 hat das Typsystem einen bedeutenden Schritt nach vorn gemacht. Union Types ermöglichen es, für Parameter, Rückgabewerte und Properties mehrere erlaubte Typen gleichzeitig anzugeben. Statt auf PHPDoc Annotationen angewiesen zu sein, lässt sich die Typflexibilität nun direkt im Code ausdrücken. Dieses Tutorial erklärt die Syntax mit dem Pipe Operator, zeigt praxisnahe Codebeispiele und beleuchtet die Regeln, Einschränkungen sowie den Unterschied zu Intersection Types.

Der erste Abschnitt erklärt die grundlegende Syntax und zeigt, wie Union Types bei der Typpürfung zur Laufzeit arbeiten.
Was sind Union Types?
Ein Union Type gibt an, dass ein Wert einen von mehreren definierten Typen haben darf. Die einzelnen Typen werden mit dem Pipe Operator | getrennt. PHP prüft zur Laufzeit, ob der übergebene Wert einem der angegebenen Typen entspricht, und wirft andernfalls einen TypeError.
<?php
function formatiere(int|float $zahl): string
{
return number_format($zahl, 2, ',', '.');
}
echo formatiere(1234);
/* 1.234,00 */
echo formatiere(1234.567);
/* 1.234,57 */
In diesem Beispiel akzeptiert die Funktion formatiere() sowohl int als auch float Werte. Vor PHP 8.0 war eine solche Typdeklaration nur über PHPDoc Kommentare möglich, die jedoch keine Laufzeitprüfung auslösten. Mit Union Types findet die Typpürfung nun automatisch statt und verbessert so die Zuverlässigkeit des Codes erheblich.
Union Types für Funktionsparameter
Der häufigste Einsatzbereich von Union Types sind Funktionsparameter. Viele Funktionen müssen mit unterschiedlichen Eingabetypen umgehen können, etwa wenn eine ID sowohl als int als auch als string vorliegen kann.
<?php
function sucheProdukt(int|string $id): string
{
$produkte = [
1 => 'Laptop',
2 => 'Tastatur',
'sonder_01' => 'Sonderposten'
];
if (is_int($id)) {
return $produkte[$id] ?? 'Nicht gefunden';
}
return $produkte[$id] ?? 'Nicht gefunden';
}
echo sucheProdukt(1);
/* Laptop */
echo sucheProdukt('sonder_01');
/* Sonderposten */
Die Typdeklaration int|string signalisiert klar, welche Typen erlaubt sind. Innerhalb der Funktion kann mit is_int() oder is_string() geprüft werden, welcher Typ tatsächlich übergeben wurde. So bleibt die Logik transparent und die IDE kann präzise Autovervollständigung anbieten.
Union Types als Rückgabetyp
Auch Rückgabewerte lassen sich mit Union Types typisieren. Ein typisches Muster ist string|false, das viele eingebaute PHP Funktionen bereits verwenden.
<?php
function sucheBenutzer(int $id): string|false
{
$benutzer = [1 => 'Anna', 2 => 'Ben'];
return $benutzer[$id] ?? false;
}
$ergebnis = sucheBenutzer(3);
if ($ergebnis === false) {
echo 'Benutzer nicht gefunden.';
}
Der Rückgabetyp string|false macht explizit, dass die Funktion entweder einen String oder den Wert false zurückgibt. Seit PHP 8.0 sind false und null als eigenständige Typen in Union Types erlaubt. Der Typ true kam erst mit PHP 8.2 hinzu.
Union Types für Properties
Typed Properties profitieren ebenfalls von Union Types. Klassen können damit Properties deklarieren, die mehrere Typen annehmen dürfen.
<?php
class Antwort
{
public int|string $code;
public string|null $nachricht;
public function __construct(
int|string $code,
?string $nachricht = null
) {
$this->code = $code;
$this->nachricht = $nachricht;
}
}
$erfolg = new Antwort(200, 'OK');
$fehler = new Antwort('not_found', null);
echo $erfolg->code;
/* 200 */
echo $fehler->code;
/* not_found */
Die Property $code akzeptiert sowohl int als auch string, während $nachricht entweder ein string oder null sein darf. Die Kurzschreibweise ?string ist dabei identisch mit string|null. Beide Varianten sind gültig, wobei die explizite Schreibweise mit |null bei längeren Union Types besser lesbar ist.
Regeln und Einschränkungen
Union Types unterliegen bestimmten Regeln, die PHP zur Kompilierzeit prüft. Das folgende Diagramm zeigt, wie PHP einen übergebenen Wert gegen einen Union Type validiert.
flowchart TD
A["Union Type: int|string|null"] --> B{Wert prüfen}
B -->|int| C[Akzeptiert]
B -->|string| C
B -->|null| C
B -->|array| D[TypeError]
B -->|object| D
Mehrere wichtige Einschränkungen sind zu beachten. Der Typ void darf nicht in einer Union verwendet werden, da er bedeutet, dass eine Funktion keinen Rückgabewert hat. Ebenso ist never ausgeschlossen. Redundante Typen wie int|int oder bool|false führen zu einem Kompilierfehler, da false bereits in bool enthalten ist. Bei der Vererbung gelten die Regeln der Kovarianz und Kontravarianz: Eine Kindklasse darf den Rückgabetyp einschränken, muss aber Parametertypen gleich breit oder breiter halten.
<?php
declare(strict_types=1);
function berechne(int|float $wert): int|float
{
return $wert * 2;
}
echo berechne(5);
/* 10 */
echo berechne(3.5);
/* 7 */
/* TypeError: Argument must be of type int|float, string given */
/* berechne("abc"); */
Im strikten Modus (strict_types=1) führt die Übergabe eines nicht erlaubten Typs sofort zu einem TypeError. Ohne strikten Modus versucht PHP eine automatische Typumwandlung, was zu unerwartetem Verhalten führen kann. Für zuverlässige Typpürfung empfiehlt sich daher die Verwendung von declare(strict_types=1) am Anfang jeder Datei.
Union Types vs. Intersection Types
Während Union Types mit | angeben, dass ein Wert einen der aufgeführten Typen haben darf, verlangen Intersection Types mit & dass ein Wert alle angegebenen Typen gleichzeitig erfüllt. Intersection Types wurden mit PHP 8.1 eingeführt. Seit PHP 8.2 lassen sich beide Konzepte mit DNF Types (Disjunctive Normal Form) kombinieren.
<?php
interface Loggable
{
public function log(): void;
}
interface Serializable
{
public function serialize(): string;
}
/* Union Type: eines von beiden */
function verarbeiteUnion(Loggable|Serializable $obj): void
{
/* $obj implementiert Loggable ODER Serializable */
}
/* Intersection Type (ab PHP 8.1): beides gleichzeitig */
function verarbeiteIntersection(Loggable&Serializable $obj): void
{
/* $obj implementiert Loggable UND Serializable */
$obj->log();
$obj->serialize();
}
Union Types eignen sich, wenn eine Funktion mit verschiedenen Eingabetypen umgehen soll. Intersection Types sind die richtige Wahl, wenn ein Objekt mehrere Interfaces gleichzeitig implementieren muss. In der Praxis kommen Union Types deutlich häufiger zum Einsatz, da sie das klassische Problem der Typflexibilität lösen.
Vor PHP 8: Die Alternative mit PHPDoc
Vor PHP 8.0 war die einzige Möglichkeit, mehrere Typen zu dokumentieren, der Einsatz von PHPDoc Annotationen. Diese boten jedoch keine Laufzeitprüfung und dienten lediglich als Hinweis für IDEs und Werkzeuge zur statischen Analyse.
<?php
/**
* @param int|string $id
* @return string|false
*/
function alteSuche($id)
{
$daten = [1 => 'Eintrag'];
return $daten[$id] ?? false;
}
Der entscheidende Nachteil dieser Methode war, dass PHP den tatsächlich übergebenen Typ nicht validierte. Fehlerhafte Aufrufe mit inkompatiblen Typen fielen erst zur Laufzeit auf, oft an einer ganz anderen Stelle im Code. Union Types beseitigen dieses Problem, indem sie die Typdeklaration in die Sprache selbst verlagern und eine sofortige Prüfung ermöglichen.
Fazit
Union Types gehören zu den wichtigsten Neuerungen von PHP 8.0. Sie machen Typdeklarationen flexibler und gleichzeitig sicherer, da die Prüfung direkt zur Laufzeit erfolgt. Die Syntax mit dem Pipe Operator | ist intuitiv und wird von allen modernen IDEs unterstützt. Zusammen mit Intersection Types seit PHP 8.1 und DNF Types seit PHP 8.2 bietet PHP ein leistungsfähiges Typsystem, das den Anforderungen moderner Anwendungen gerecht wird. Wer bisher auf PHPDoc Annotationen angewiesen war, sollte bestehenden Code schrittweise auf native Union Types umstellen, um von der automatischen Typpürfung und der verbesserten statischen Analyse zu profitieren.