Ein PHP-Upgrade auf 8.x ist kein Selbstzweck. Der eigentliche Gewinn liegt in den Features, die man danach einsetzen kann – und die im Legacy-Kontext sofort spürbare Verbesserungen bringen. Ich muss nicht erst die gesamte Architektur umbauen. Ich kann heute anfangen, indem ich einzelne Stellen lesbarer mache.

Named Arguments: Parameterlisten endlich verstehen

Wer kennt das nicht: eine Funktion mit sieben Parametern, die Hälfte davon optional, und man starrt auf den Aufruf und fragt sich, was true, false, null, true bedeutet.

// Vorher
$result = createUser('Mario', 'mario@example.com', true, false, null, true, 3);

// Nachher – Named Arguments
$result = createUser(
    name: 'Mario',
    email: 'mario@example.com',
    isActive: true,
    sendWelcomeMail: true,
    role: 3
);

Ich muss die Funktion nicht anfassen. Der Aufrufer wird sofort lesbar. Das ist Low-hanging Fruit der saubersten Sorte.

Match-Expression: Schluss mit isset-Ketten

Switch-Blöcke mit fehlendem break, verschachtelte ternäre Ausdrücke, endlose isset-Ketten – die Match-Expression ersetzt das alles durch etwas, das sich wie eine ehrliche Aussage liest.

// Vorher
if (isset($status) && $status === 'active') {
    $label = 'Aktiv';
} elseif (isset($status) && $status === 'locked') {
    $label = 'Gesperrt';
} else {
    $label = 'Unbekannt';
}

// Nachher
$label = match($status ?? '') {
    'active'  => 'Aktiv',
    'locked'  => 'Gesperrt',
    default   => 'Unbekannt',
};

Match wirft außerdem eine UnhandledMatchError-Exception, wenn kein Arm passt und kein Default definiert ist – das ist ein Sicherheitsnetz, das switch nie hatte.

Nullsafe Operator: Verschachtelte isset-Prüfungen eliminieren

Legacy-Code ist oft gepflastert mit defensiven isset-Konstrukten, die sich über mehrere Zeilen ziehen. Der Nullsafe Operator (?->) macht das kompakt und klar.

// Vorher
$city = null;
if (isset($user) && $user->getAddress() !== null) {
    $city = $user->getAddress()->getCity();
}

// Nachher
$city = $user?->getAddress()?->getCity();

Das ist kein Trick – es ist genau das, was der Code schon die ganze Zeit sagen wollte.

Enums: Schluss mit Magic Numbers

Zahlen wie 1, 2, 99 als Statuscodes sind in Legacy-Code gang und gäbe. Enums schaffen Klarheit, ohne dass ich bestehende Datenbankstrukturen anfassen muss.

// Vorher
if ($order->status === 2) { /* bezahlt */ }

// Nachher
enum OrderStatus: int {
    case Pending = 1;
    case Paid    = 2;
    case Shipped = 3;
}

if ($order->status === OrderStatus::Paid->value) { /* bezahlt */ }

Union Types: Dokumentation, die der Code selbst erzwingt

Statt PHPDoc-Kommentaren, die niemand aktuell hält, schreibe ich jetzt direkt in die Signatur, was eine Funktion akzeptiert.

// Vorher (oder gar kein Typ)
/** @param int|string $id */
function findUser($id) { ... }

// Nachher
function findUser(int|string $id): ?User { ... }
Diese Features verbessern die Lesbarkeit sofort – auch wenn der Rest der Datei noch aussieht wie 2009. Ich muss nicht alles auf einmal modernisieren. Jede Stelle, die ich anfasse, kann ich besser hinterlassen als ich sie vorgefunden habe.

Fazit

PHP 8.x ist kein Grund für einen Big-Bang-Umbau. Es ist eine Einladung, kleinere Verbesserungen gezielt dort einzusetzen, wo ich sowieso gerade arbeite. Named Arguments, Match, Nullsafe Operator, Enums und Union Types kosten mich beim Schreiben nicht mehr Zeit – aber sie ersparen dem nächsten Entwickler (oder mir selbst in drei Monaten) erheblich viel Rätselraten.