Unreachable Code
debt(d5/e1/b3/t7)
Closest to 'specialist tool catches it' (d5), because detection_hints.tools lists phpstan, psalm, and rector — all specialist static analysis tools requiring explicit configuration (e.g., PHPStan level 4+). It is not caught by the compiler or a default linter, but it is reliably caught once these tools are enabled.
Closest to 'one-line patch or single-call swap' (e1). The quick_fix confirms enabling PHPStan level 4+ surfaces the issues, and common_mistakes show the fixes are local: move cleanup code before return, remove code after throw, delete dead else blocks. Each instance is a small, local edit.
Closest to 'localised tax' (b3). Unreachable code is a per-function quality issue — it doesn't spread architectural gravity across the codebase. It applies broadly (web, cli, queue-worker) but each instance is self-contained, imposing maintenance friction only in the affected file rather than shaping the whole system.
Closest to 'serious trap' (t7). The misconception field states developers believe unreachable code after a return is 'harmless' when it actually signals a logic error in what was supposed to run before the return. This contradicts intuition: code is silently skipped rather than causing any obvious failure, meaning the real bug (wrong placement of logic) is hidden rather than surfaced.
Also Known As
TL;DR
Explanation
Unreachable code appears after unconditional return/throw/exit/break/continue, inside dead conditions, or in catch blocks that can never be triggered. It is always a defect: either the code was meant to execute (logic error) or it is leftover debris from refactoring. PHPStan and Psalm report unreachable statements. The never return type in PHP 8.1 lets static analysers know that certain functions never return, improving detection.
Common Misconception
Why It Matters
Common Mistakes
- Placing cleanup code after return instead of before it.
- Adding code after a throw in an exception handler — the throw terminates the current path.
- A catch block for an exception the try block cannot throw — wastes maintenance effort.
- Early return refactoring that leaves else blocks unreachable but visually present.
Code Examples
// Code after return — never executes:
function processOrder(Order $order): string {
return 'processed';
$this->audit->log($order); // Never runs — silently skipped!
$order->markComplete(); // Never runs
}
// Unreachable catch:
try {
$result = purePHPCalculation($x); // Cannot throw
} catch (DatabaseException $e) { // Unreachable
handleError($e);
}
// Correct order — execution before return:
function processOrder(Order $order): string {
$this->audit->log($order); // Runs first
$order->markComplete(); // Runs second
return 'processed'; // Returns last
}
// Remove unreachable catch:
$result = purePHPCalculation($x); // No try needed