🚧 Beta: DeadCodeBundle is in active development. APIs, configuration keys and storage formats may still change. All feedback, bug reports and pull requests are very welcome!
A Symfony bundle that detects dead code in production by sampling real-traffic line coverage with PCOV and aggregating the results in Redis, so you can see exactly which lines of your application are never executed by real users.
Unlike static analysis or test-coverage tools, DeadCodeBundle measures coverage from live HTTP traffic. On each request (or a sample of them), it starts PCOV, lets the request run, then stops PCOV and stores the per-line hit counts in Redis. Over time this builds a picture of which files, classes and lines are actually exercised in production versus code that is never reached — a strong signal for safe removal candidates.
- Live production coverage — collects real per-line execution data via PCOV during actual HTTP requests, not synthetic tests.
- Configurable sampling — collect coverage on a percentage of requests (
sampling_rate) to keep overhead negligible on high-traffic apps. - Redis-backed aggregation — coverage from many requests/processes is merged and stored in Redis with a configurable TTL, so data self-expires and stays fresh.
- Path exclusion — ignore vendor code, cache, tests, or any other paths via
ignored_paths. - Web dashboard — a built-in
/dead-code/dashboardpage showing global coverage percentage, per-file breakdowns, and source code with line-by-line coverage highlighting. - JSON API —
/dead-code/apiexposes the same data for scripting or external tooling. - CLI report —
php bin/console dead-code:reportprints a coverage summary and highlights the least-covered files, ideal for CI or terminal use. - One-click reset — clear all collected coverage data from the dashboard or via the
/dead-code/clearroute.
Configuration lives under the dead_code key, typically in config/packages/dead_code.yaml:
dead_code:
enabled: false # master on/off switch
redis_dsn: "redis://localhost:6379" # Redis connection used to store coverage
sampling_rate: 100 # % of requests to sample (1-100)
cache_ttl: 86400 # seconds before stored coverage entries expire
ignored_paths: # substrings matched against reported file paths
- "vendor/"
- "var/cache/"
- "tests/"| Key | Type | Default | Description |
|---|---|---|---|
enabled |
bool | false |
Enables request collection. Keep false in environments where you don't want overhead. |
redis_dsn |
string | redis://localhost:6379 |
DSN passed to the Redis client used for storage. |
sampling_rate |
int | 100 |
Percentage (1-100) of main requests that are actually profiled. |
cache_ttl |
int | 86400 |
TTL (seconds) applied to stored coverage entries in Redis. |
ignored_paths |
list | ['vendor/', 'var/cache/', 'tests/'] |
Paths excluded from collected/reported coverage. |
The bundle registers three routes (see config/routing.php):
| Route | Path | Method | Description |
|---|---|---|---|
dead_code_dashboard |
/dead-code/dashboard |
GET | HTML dashboard with global stats and per-file view. |
dead_code_api |
/dead-code/api |
GET | Same data as the dashboard, as JSON. |
dead_code_clear |
/dead-code/clear |
POST | Clears all stored coverage data. |
php bin/console dead-code:reportPrints the global coverage percentage and lists every file with less than 100% coverage.
CoverageListener::onKernelRequestdecides whether to collect (based onenabledandsampling_rate) and starts PCOV for the current request.CoverageListener::onKernelTerminatestops PCOV, normalizes the collected line hits (PCOV's-1sentinel for "executable but not executed" is clamped to0), and stores them.RedisStoragemerges hit counts across requests/processes by summing them, keyed with the configuredcache_ttl.CoverageReporterreads back the aggregated data, reads matching source files from disk, and builds the per-file/per-line breakdown used by the dashboard, API and CLI command.
Make sure Composer is installed globally, as explained in the installation chapter of the Composer documentation.
- PHP 8.1 or higher
- the
pcovPHP extension (php -m | grep pcovto check) - a reachable Redis server
- Symfony 6.4, 7.0 or 8.0 (
symfony/framework-bundle,symfony/console,symfony/routing) - optionally
symfony/twig-bundle, to render the HTML dashboard
composer require kinoba/dead-code-bundleIf the pcov extension isn't installed yet, Composer will refuse the requirement. Either
install the extension first (pecl install pcov), or, for local testing only, bypass the
platform check:
composer require kinoba/dead-code-bundle --ignore-platform-req=ext-pcovIf you don't use Symfony Flex, enable the bundle by adding it to
config/bundles.php:
// config/bundles.php
return [
// ...
Kinoba\DeadCodeBundle\DeadCodeBundle::class => ['all' => true],
];Register the bundle's routes so the dashboard, API and clear endpoints are reachable, e.g. in
config/routes/dead_code.yaml:
dead_code:
resource: "@DeadCodeBundle/config/routing.php"
type: phpCreate config/packages/dead_code.yaml:
dead_code:
enabled: "%env(bool:DEAD_CODE_ENABLED)%"
redis_dsn: "%env(DEAD_CODE_REDIS_DSN)%"
sampling_rate: 10
cache_ttl: 86400
ignored_paths:
- "vendor/"
- "var/cache/"
- "tests/"Then set the corresponding variables in your .env (or .env.local) file:
DEAD_CODE_ENABLED=1
DEAD_CODE_REDIS_DSN=redis://localhost:6379Start with a low sampling_rate (e.g. 10) on production traffic to keep overhead minimal,
and increase it if you need more data faster on lower-traffic environments.
- Make a few requests to your application with
enabled: true. - Visit
/dead-code/dashboardand confirm coverage data is appearing. - Or run
php bin/console dead-code:reportto see a summary in your terminal.
If the dashboard is empty, double-check that the pcov extension is loaded and that
redis_dsn points to a reachable Redis instance.
Contributions are very welcome, especially while the bundle is in beta!
-
Fork the repository and create a branch for your change.
-
Install dependencies:
composer install. -
Run the test suite before opening a PR:
vendor/bin/phpunit -
Keep changes focused and add/update tests for any behavior change.
-
Open a pull request describing the motivation and the change.
If you run into a bug, have a question, or want to suggest an improvement, please open an issue — feedback at this stage is especially valuable and directly shapes the bundle.
This project is supported by Kinoba
Released under the MIT License