Der never Return Type in PHP 8.1: Funktionen ohne Rückkehr
AI generated
8.4
PHP · never Return Type · PHP 8.1
Der never Return Type
Funktionen, die garantiert nicht zurückkehren

void bedeutet, eine Funktion liefert keinen sinnvollen Rückgabewert, kehrt aber trotzdem normal zurück. never bedeutet etwas fundamental anderes: Die Funktion kehrt überhaupt nicht zurück, sie wirft immer eine Exception oder beendet das Skript. Seit PHP 8.1 lässt sich dieser Unterschied direkt in der Signatur ausdrücken.

9 Min. Lesezeit Typsystem PHP 8.1+

1. Was never bedeutet und wie es sich von void unterscheidet

void beschreibt eine Funktion, die keinen Rückgabewert liefert, aber nach ihrer Ausführung ganz normal zum Aufrufer zurückkehrt, der Kontrollfluss läuft direkt hinter dem Funktionsaufruf weiter. never dagegen beschreibt eine Funktion, die den Kontrollfluss niemals zurückgibt: Entweder wirft sie garantiert eine Exception, oder sie beendet den Prozess über exit oder die.

Dieser Unterschied ist keine reine Formalität, er verändert, was der Aufrufer über den Code nach dem Funktionsaufruf annehmen darf. Nach einem void-Aufruf muss der folgende Code weiterhin mit jedem möglichen Zustand rechnen, nach einem never-Aufruf ist der folgende Code aus Sicht des Typsystems schlicht unerreichbar.

Diese Unterscheidung existiert in vielen statisch typisierten Sprachen unter verschiedenen Namen, etwa als Bottom Type oder Never Type, und beschreibt dort dasselbe Konzept: einen Typ, für den es per Definition keine gültige Instanz und keine normale Rückkehr aus einer Funktion geben kann.

2. Wann eine Funktion never statt void deklarieren sollte

never gehört immer dann in die Signatur, wenn eine Funktion garantiert und ausnahmslos entweder eine Exception wirft oder das Skript über exit oder die beendet, ohne jeden Codepfad, der stattdessen normal zurückkehrt. Schon ein einziger Pfad mit einem regulären return macht never zur falschen Wahl.

Typische Kandidaten sind reine Fehlerbehandlungs-Funktionen wie throwNotFound(), Assertion-Helfer, die bei fehlgeschlagener Bedingung immer eine Exception werfen, oder Bootstrap-Skripte, die bei einem fatalen Konfigurationsfehler das Programm sofort beenden.

Eine hilfreiche Faustregel bei der Entscheidung: Lässt sich für eine Funktion kein einziges realistisches Szenario finden, in dem sie normal zum Aufrufer zurückkehrt, ist never fast immer die präzisere und ehrlichere Deklaration als void, weil sie diese Garantie für Menschen und Werkzeuge gleichermaßen sichtbar macht.


function throwNotFound(string $resource): never
{
    throw new NotFoundException("Resource not found: {$resource}");
}

3. Syntax und Regeln: never darf nicht kombiniert werden

never lässt sich anders als die meisten Typen nicht mit anderen Typen zu einem Union Type kombinieren, denn never bedeutet bereits die leere Menge aller möglichen Rückgabewerte. Ein Ausdruck wie never|string ergibt logisch keinen Sinn und wird vom Parser abgelehnt.

Eine Funktion, die als never deklariert ist, aber tatsächlich einen Pfad mit regulärem return besitzt, egal ob mit oder ohne Wert, führt zu einem Fatal Error zur Laufzeit, sobald dieser Pfad ausgeführt wird. Der Compiler prüft die Vollständigkeit dieser Garantie nicht statisch, das übernehmen erst externe Analyse-Werkzeuge.


// Fatal error at runtime once this path executes: never must not return
function validate(int $value): never
{
    if ($value < 0) {
        throw new InvalidArgumentException('Value must be non-negative');
    }

    return; // invalid: this path returns normally
}

4. Nutzen für die statische Analyse

Der eigentliche Mehrwert von never zeigt sich nicht zur Laufzeit, sondern in Werkzeugen wie PHPStan und Psalm: Sobald eine Analyse einen Aufruf einer never-Funktion erkennt, markiert sie jeden Code direkt danach als unerreichbar und meldet ihn, statt ihn stillschweigend zu ignorieren.

Das ist besonders wertvoll bei Type Narrowing: Wird eine Variable vor einem never-Aufruf auf einen bestimmten Typ eingegrenzt, kann das Analyse-Tool nach dem Aufruf zuverlässig annehmen, dass der ausgeschlossene Fall gar nicht mehr existiert, weil der Kontrollfluss diesen Zweig nie regulär verlässt.


function process(?User $user): void
{
    if ($user === null) {
        throwNotFound('user');
    }

    // PHPStan narrows $user to User here, the null case is provably unreachable
    echo $user->getName();
}

5. never in abstrakten Methoden und Interfaces

never lässt sich auch in abstrakten Methoden und Interface-Deklarationen verwenden, um verpflichtend festzulegen, dass jede Implementierung niemals normal zurückkehren darf. Das ist nützlich für Contracts wie ein FatalHandlerInterface, bei dem jede konkrete Implementierung garantiert eine Exception werfen oder das Programm beenden muss.

Wichtig dabei: Eine Implementierung darf den Rückgabetyp verengen, aber niemals erweitern, never in einer Interface-Signatur zwingt also jede Implementierung ebenfalls zu never oder zu einem noch spezifischeren Verhalten, ein Abweichen zu void wäre ein Vertragsbruch und ein Fehler bei der Typkompatibilität.

6. Zusammenspiel mit der throw-Expression seit PHP 8.0

Seit PHP 8.0 ist throw eine Expression statt einer reinen Anweisung, was es erlaubt, throw etwa in einem Null-Coalescing-Ausdruck oder einem ternären Operator zu verwenden. never ergänzt dieses Feature auf Funktionsebene, indem es dieselbe Garantie einer Funktion als Ganzes zuschreibt, die throw für eine einzelne Expression liefert.

Kombiniert man beide Features, lässt sich eine kompakte Helper-Funktion bauen, die selbst als never deklariert ist und intern throw als Expression nutzt, um verschiedene Fehlerarten je nach Bedingung unterschiedlich zu werfen, ohne mehrzeilige if-Blöcke.

Auch innerhalb einer einzigen Zeile lassen sich beide Features so kombinieren, dass eine kurze Guard-Klausel am Anfang einer Methode sowohl die Prüfung als auch die Fehlerbehandlung übernimmt, ohne den eigentlichen Methodenkörper mit zusätzlicher Verschachtelung zu belasten.

7. Häufige Fehler: never bei Funktionen, die doch manchmal zurückkehren

Der häufigste Fehler bei never entsteht, wenn eine Funktion in der Theorie immer eine Exception wirft, in der Praxis aber einen übersehenen Pfad besitzt, etwa eine switch-Anweisung ohne default-Zweig, die bei einem unerwarteten Wert einfach durchfällt und implizit null zurückgibt.

Solche Fehler bleiben zur Compile-Zeit unentdeckt und fallen erst als Fatal Error zur Laufzeit auf, sobald der fehlende Pfad tatsächlich durchlaufen wird. Ein match-Ausdruck statt switch reduziert dieses Risiko deutlich, weil match bei fehlendem Treffer automatisch eine UnhandledMatchError wirft, statt still durchzufallen.


function dispatch(string $action): never
{
    match ($action) {
        'delete' => throw new ForbiddenException('Action not allowed'),
        'legacy' => exit('Legacy action terminated'),
        // No default needed: match throws UnhandledMatchError automatically
    };
}

8. Praxisbeispiel: never in einem Router und Dispatcher

In einem einfachen Router lässt sich never gezielt für die Methode einsetzen, die bei fehlender Route reagiert. Statt eine 404-Antwort als regulären Rückgabewert zu modellieren, wirft die Methode eine spezifische Exception, die eine übergeordnete Fehlerbehandlung in eine echte HTTP-Antwort umwandelt.

Der Vorteil zeigt sich direkt im aufrufenden Code: Nach dem Aufruf von notFound() muss keine zusätzliche Prüfung mehr folgen, weder eine Rückgabewert-Prüfung noch eine Nullable-Behandlung, weil das Typsystem selbst garantiert, dass dieser Pfad nie normal fortgesetzt wird.


final class Router
{
    public function dispatch(string $path): Response
    {
        foreach ($this->routes as $route) {
            if ($route->matches($path)) {
                return $route->handle();
            }
        }

        $this->notFound($path); // never returns, no code after this needs a null check
    }

    private function notFound(string $path): never
    {
        throw new RouteNotFoundException("No route matches: {$path}");
    }
}

9. never in Verbindung mit exit und Middleware-Stacks

Neben Exceptions ist exit beziehungsweise die der zweite legitime Weg, um eine never-Garantie zu erfüllen, etwa in einem CLI-Skript, das bei einem fatalen Konfigurationsfehler sofort mit einem Exit-Code beenden soll, ohne dass der restliche Aufrufstapel weiterläuft.

In Middleware-Stacks ist Vorsicht geboten: Eine Middleware, die never deklariert, unterbricht die gesamte Verarbeitungskette endgültig, es gibt keine Möglichkeit für nachfolgende Middleware, noch einzugreifen. never sollte deshalb nur für tatsächlich terminale Fehlerfälle verwendet werden, nicht für reguläre Kontrollfluss-Entscheidungen innerhalb einer Pipeline.

Ein hilfreiches Gedankenexperiment für die Praxis: Wer sich fragt, ob eine Middleware wirklich niemals normal zurückkehren soll, sollte prüfen, ob es einen einzigen denkbaren Produktionsfall gibt, in dem die Anfrage trotz Fehlers weiterverarbeitet werden könnte, denn schon ein einziger solcher Fall spricht gegen never.

Merkmal void never
Kehrt die Funktion zurück? Ja, ohne Rückgabewert Nein, niemals
Code nach dem Aufruf Wird normal weiter ausgeführt Gilt als unerreichbar
Kombinierbar mit Union Types Ja, in bestimmten Kontexten Nein, niemals kombinierbar
Typischer Anwendungsfall Setter, Logging-Methoden Fehlerbehandlung, exit, Assertion-Helfer
Fehler bei Verstoß Kein Fehler, void toleriert jeden Rückgabewert-Verzicht Fatal Error bei regulärem return
Nutzen für Type Narrowing Kein besonderer Effekt Analyse-Tools schließen den Fall zuverlässig aus

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

never Return Type

Kernidee

never signalisiert, dass eine Funktion garantiert niemals normal zum Aufrufer zurückkehrt.

Abgrenzung

void kehrt ohne Wert zurück, never kehrt überhaupt nicht zurück, wirft oder beendet den Prozess.

Nutzen

PHPStan und Psalm markieren Code nach einem never-Aufruf zuverlässig als unerreichbar.

Vorsicht

Ein einziger regulärer return-Pfad in einer never-Funktion führt zu einem Fatal Error zur Laufzeit.

11. FAQ: never Return Type

1Was ist der Unterschied zwischen void und never?
void kehrt ohne Rückgabewert normal zum Aufrufer zurück, never kehrt überhaupt nicht zurück, sondern wirft immer eine Exception oder beendet das Programm.
2Seit welcher PHP Version gibt es den never Type?
Der never Return Type wurde mit PHP 8.1 eingeführt, zusammen mit Enums, readonly Properties und mehreren anderen Features.
3Kann never mit anderen Typen kombiniert werden?
Nein, never lässt sich nicht in einem Union Type kombinieren, weil es bereits die leere Menge aller möglichen Rückgabewerte repräsentiert.
4Was passiert, wenn eine never-Funktion doch zurückkehrt?
Sobald der Codepfad mit einem regulären return ausgeführt wird, wirft PHP zur Laufzeit einen Fatal Error, der Compiler prüft das nicht statisch selbst.
5Wofür ist never bei der statischen Analyse nützlich?
PHPStan und Psalm markieren Code direkt nach einem never-Aufruf als unerreichbar und nutzen die Garantie für präziseres Type Narrowing.
6Kann eine abstrakte Methode never deklarieren?
Ja, und jede Implementierung ist dann verpflichtet, ebenfalls never einzuhalten, ein Abweichen zu void wäre ein Vertragsbruch.
7Sollte jede Exception-werfende Funktion never nutzen?
Nur wenn die Funktion ausnahmslos immer wirft oder das Programm beendet, ein einziger normaler Rückgabepfad macht never zur falschen Wahl.
8Wie hängt never mit der throw-Expression zusammen?
throw als Expression seit PHP 8.0 liefert dieselbe Garantie für einen einzelnen Ausdruck, die never für eine ganze Funktion beschreibt.
9Ist never für Middleware-Stacks geeignet?
Nur für tatsächlich terminale Fehlerfälle, da eine never-Middleware die gesamte Verarbeitungskette endgültig unterbricht, ohne Chance auf nachfolgende Verarbeitung.
10Hilft ein match-Ausdruck bei never-Funktionen?
Ja, match wirft bei fehlendem Treffer automatisch eine UnhandledMatchError, was übersehene Pfade in never-Funktionen deutlich unwahrscheinlicher macht.