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.
Inhaltsverzeichnis
- 1. Was never bedeutet und wie es sich von void unterscheidet
- 2. Wann eine Funktion never statt void deklarieren sollte
- 3. Syntax und Regeln: never darf nicht kombiniert werden
- 4. Nutzen für die statische Analyse
- 5. never in abstrakten Methoden und Interfaces
- 6. Zusammenspiel mit der throw-Expression seit PHP 8.0
- 7. Häufige Fehler: never bei Funktionen, die doch manchmal zurückkehren
- 8. Praxisbeispiel: never in einem Router und Dispatcher
- 9. never in Verbindung mit exit und Middleware-Stacks
- 10. Zusammenfassung
- 11. FAQ
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.