Dead Code Detection
Also Known As
TL;DR
Explanation
Dead code accumulates over time: unreachable branches after a return or throw, conditions that are always true or false due to type constraints, methods never called from anywhere, and variables written but never read. It increases cognitive load (readers wonder why it exists), masks real logic, and can hide bugs (the 'dead' branch may have been reached and mattered at some point). Detection tools: PHPStan level 4+ reports always-true/false conditions and unreachable code; Psalm's dead-code analysis finds uncalled methods with --find-dead-code; code coverage reports (Xdebug + PHPUnit) highlight never-executed lines. Remove dead code promptly — git history preserves it if needed. Never comment it out: commented code is worse than deleted code.
Common Misconception
Why It Matters
Common Mistakes
- Relying solely on manual code review to find dead code in large codebases — it scales poorly.
- Not running PHPStan or Psalm at maximum level — lower levels skip unused method and property checks.
- Deleting flagged dead code without checking if it's used via Reflection or dynamic calls like call_user_func.
- Treating dead code warnings as low priority — they often indicate structural problems worth investigating.
Code Examples
class UserService {
public function getUser(int $id): User { /* used */ }
public function legacyFetch(int $id): User { /* never called — dead */ }
private function internalHelper(): void { /* unreferenced — dead */ }
}
// PHP tools that find dead code:
// PHPStan — flags unreachable code and unused methods:
$ vendor/bin/phpstan analyse --level=6 src/
// 'Call to an always-true condition'
// 'Return type is never used'
// Psalm — taint analysis also finds dead paths:
$ vendor/bin/psalm --find-dead-code
// php-dead-code-detector:
$ composer require --dev povils/phpmnd
// IDE support:
// PhpStorm highlights unreachable code, unused private methods in grey
// Git approach — blame + coverage:
// Lines never covered by tests AND never changed in 12 months = candidates
$ vendor/bin/phpunit --coverage-text | grep -v ' 100%'
// Remove dead code — don't comment it out; version control preserves history