phpunit/php-code-coverage
phpunit/php-code-coverage collects, processes, and renders PHP code coverage data. Use it to start/stop coverage during tests, filter included files, and generate reports such as OpenClover XML from live runs or serialized coverage data.
Installation:
composer require --dev phpunit/php-code-coverage
Add as a dev dependency to avoid bloating production builds.
Basic Usage:
use SebastianBergmann\CodeCoverage\CodeCoverage;
use SebastianBergmann\CodeCoverage\Driver\Selector;
use SebastianBergmann\CodeCoverage\Filter;
use SebastianBergmann\CodeCoverage\Report\Facade;
$filter = (new Filter)->includeFiles([__DIR__.'/src/**/*.php']);
$coverage = new CodeCoverage((new Selector)->forLineCoverage($filter), $filter);
$coverage->start('test-suite');
// Execute tests or code under test
$coverage->stop();
Facade::fromObject($coverage)->renderHtml(__DIR__.'/coverage-report');
Key Classes to Know:
CodeCoverage: Core class for collecting coverage data.Filter: Define inclusion/exclusion rules for files/methods.Report\Facade: Unified interface for generating reports (HTML, XML, etc.).Serialization\Serializer/Unserializer: Save/load coverage data for later use.Test-Driven Coverage:
// In a test bootstrap file (e.g., `tests/bootstrap.php`)
$coverage = new CodeCoverage((new Selector)->forLineCoverage(), new Filter);
$coverage->start('unit-tests');
// Run tests (e.g., via PHPUnit or custom runner)
$coverage->stop();
// Generate report
Facade::fromObject($coverage)->renderHtml(__DIR__.'/coverage');
CI Pipeline Integration:
# Serialize coverage data after tests
php vendor/bin/phpunit --coverage-php coverage.php
# In CI (e.g., GitHub Actions), generate reports
php -r "require 'vendor/autoload.php'; \SebastianBergmann\CodeCoverage\Report\Facade::fromSerializedData(unserialize(file_get_contents('coverage.php')))->renderClover('clover.xml');"
Custom Test Runners:
// Example: Custom test runner with coverage
$coverage = new CodeCoverage((new Selector)->forLineCoverage(), new Filter);
$coverage->start('custom-runner');
// Execute test cases
foreach ($testCases as $case) {
$case->run();
}
$coverage->stop();
Facade::fromObject($coverage)->renderXml(__DIR__.'/coverage.xml');
PHPUnit Integration:
Use --coverage-php to generate serialized coverage files, then process them with this library for custom reports.
phpunit --coverage-php coverage.cov
php -r "require 'vendor/autoload.php'; \SebastianBergmann\CodeCoverage\Report\Facade::fromSerializedData(unserialize(file_get_contents('coverage.cov')))->renderHtml('report');"
Filtering: Exclude vendor files or specific directories:
$filter = (new Filter)
->includeFiles([__DIR__.'/src/**/*.php'])
->excludeFiles([__DIR__.'/src/Exceptions/**']);
Report Formats:
Supported formats via Facade:
renderHtml(): Interactive HTML reports with charts.renderClover()/renderOpenClover(): XML for tools like SonarQube.renderText(): CLI-friendly text output.Performance:
Use CacheWarmer to pre-warm static analysis caches for faster report generation in CI:
$cacheWarmer = new \SebastianBergmann\CodeCoverage\CacheWarmer();
$cacheWarmer->warmCache();
Driver Conflicts:
Selector to explicitly choose one:
(new Selector)->forLineCoverage(new Filter, 'pcov'); // or 'xdebug'
Path Handling:
$data = (new Unserializer)->unserialize('coverage.cov');
// Paths in `$data` are relative to the serialization directory.
Race Conditions:
phpunit --parallel) may cause race conditions in static analysis. Use CachingSourceAnalyser carefully or disable caching:
$filter->setCache(new \SebastianBergmann\CodeCoverage\Cache\NullCache());
Deprecated Methods:
Filter::includeUncoveredFiles()/excludeUncoveredFiles() (deprecated in v11.0.9). Use includeFiles()/excludeFiles() instead.PHP 8.3+:
Invalid XML Reports: If XML reports fail validation, check for:
DOMDocument/XMLWriter.Missing Coverage Data:
Filter rules include all target files.php.ini:
; For Xdebug
xdebug.mode=coverage
xdebug.start_with_request=yes
; For PCov
extension=pcov.so
Serialization Issues:
coverage.cov) is not corrupted. Re-generate it if needed:
phpunit --coverage-php coverage.cov
Custom Report Formats:
Extend Report\Facade or implement Report\RendererInterface for new formats:
class CustomRenderer implements RendererInterface {
public function render(CodeCoverage $coverage, string $path) {
// Custom logic to generate reports
}
}
Filter Logic:
Override Filter to implement custom inclusion/exclusion rules:
class CustomFilter extends Filter {
public function includeFiles(array $files): self {
// Add custom logic (e.g., exclude files matching a pattern)
return parent::includeFiles($files);
}
}
Driver Plugins:
Add support for new coverage drivers by implementing Driver\DriverInterface:
class MyDriver implements DriverInterface {
public function start(string $name) { /* ... */ }
public function stop() { /* ... */ }
public function getData() { /* ... */ }
}
Register it with DriverSelector:
$selector = new Selector;
$selector->registerDriver('my_driver', new MyDriver());
Performance Tuning:
$filter->setCache(new \SebastianBergmann\CodeCoverage\Cache\NullCache());
SHA-256 for cache keys (faster than MD5 in PHP 8.4+):
$filter->setCache(new \SebastianBergmann\CodeCoverage\Cache\FileCache(__DIR__.'/cache'));
HTML Report Dark Mode: Enable via CSS or environment variable (if supported by your version):
Facade::fromObject($coverage)->renderHtml(__DIR__.'/report', [
'dark_mode' => true,
]);
OpenClover vs. Clover:
Use renderOpenClover() for OpenClover-compatible XML (validates against OpenClover schema). The legacy renderClover() may not validate strictly.
Line Coverage vs. Branch Coverage: Explicitly select the driver type:
(new Selector)->forLineCoverage($filter); // Line coverage
(new Selector)->forBranchCoverage($filter); // Branch coverage
How can I help you explore Laravel packages today?