jaschilz/php-coverage-badger
Generate an SVG code coverage badge from a PHPUnit Clover XML report. Install via Composer and run the included CLI to turn clover.xml into a coverage.svg badge for your CI or README.
Install the Package
composer require --dev jaschilz/php-coverage-badger
Add to composer.json under require-dev to ensure it’s included in CI environments.
Generate Clover XML
Configure PHPUnit to output Clover XML in phpunit.xml:
<logging>
<log type="coverage-clover" target="build/clover.xml"/>
</logging>
Run tests:
./vendor/bin/phpunit
Generate Badge Run the CLI command:
./vendor/bin/php-coverage-badge build/clover.xml public/badges/coverage.svg
Place the generated coverage.svg in your project’s public or storage/app/public folder.
Add a step in your CI pipeline (e.g., GitHub Actions, Travis CI) to:
gh-pages) or artifact storage.Example GitHub Actions snippet:
- name: Generate Coverage Badge
run: ./vendor/bin/php-coverage-badge build/clover.xml public/badges/coverage.svg
- name: Deploy Badge
uses: peaceiris/actions-gh-pages@v3
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
publish_dir: ./public/badges
On-Demand Badge Generation Use a Laravel Artisan command to wrap the badge generation:
// app/Console/Commands/GenerateCoverageBadge.php
namespace App\Console\Commands;
use Illuminate\Console\Command;
use Jaschilz\PHPCoverageBadger\PHPCoverageBadger;
class GenerateCoverageBadge extends Command
{
protected $signature = 'coverage:badge {clover_file? : Path to Clover XML} {output? : Output SVG path}';
protected $description = 'Generate a coverage badge from Clover XML';
public function handle()
{
$badger = new PHPCoverageBadger();
$badger->generate($this->argument('clover_file'), $this->argument('output'));
$this->info("Badge generated at {$this->argument('output')}");
}
}
Run locally:
php artisan coverage:badge build/clover.xml public/badges/coverage.svg
Dynamic Badge Paths
Store badge paths in .env:
COVERAGE_BADGE_PATH=public/badges/coverage.svg
COVERAGE_CLOVER_PATH=build/clover.xml
Reference in your command:
$badger->generate(env('COVERAGE_CLOVER_PATH'), env('COVERAGE_BADGE_PATH'));
Laravel Blade Integration Display the badge in a view:
<img src="{{ asset('badges/coverage.svg') }}" alt="Code Coverage">
Or dynamically link to a branch-specific badge:
// app/Helpers/CoverageHelper.php
public static function badgeUrl($branch = 'main')
{
return asset("badges/{$branch}/coverage.svg");
}
CI Artifact Storage Upload the badge as a CI artifact (e.g., GitHub Actions):
- name: Upload Coverage Badge
uses: actions/upload-artifact@v3
with:
name: coverage-badge
path: public/badges/coverage.svg
Custom Badge Styling
Extend the PHPCoverageBadger class to modify badge colors or labels:
class CustomBadger extends PHPCoverageBadger
{
protected function getBadgeColor(float $coverage): string
{
return $coverage > 90 ? 'brightgreen' : ($coverage > 75 ? 'green' : 'yellow');
}
protected function getBadgeLabel(float $coverage): string
{
return "Coverage: {$coverage}% (Custom)";
}
}
Clover XML Path Issues
storage_path():
$cloverPath = storage_path('logs/clover.xml');
Permission Denied
public/badges/ may fail due to permissions.mkdir -p public/badges && chmod -R 775 public/badges
Or use Laravel’s storage_path() for safer writes.Outdated Badge on CI
Deprecated Package
phpunit/phpunit).Validate Clover XML Ensure the XML is valid before processing:
xmllint --noout build/clover.xml
Common issues:
<metrics> or <project> tags.<coverage> line numbers.Check Badge Output Inspect the generated SVG for errors:
cat public/badges/coverage.svg | grep -A 10 "Invalid"
Expected structure:
<svg>...</svg>
If empty, the Clover XML was likely malformed.
Log Coverage Data Debug the parsed coverage data:
$badger = new PHPCoverageBadger();
$data = $badger->parseCloverFile($cloverPath);
\Log::info('Coverage Data:', $data);
Custom Thresholds Override the default "passing" threshold (e.g., 80%):
class StrictBadger extends PHPCoverageBadger
{
protected function isCoverageAcceptable(float $coverage): bool
{
return $coverage >= 80;
}
}
Badge Templates
Extend the SVG template by overriding the getBadgeTemplate() method:
protected function getBadgeTemplate(): string
{
return file_get_contents(__DIR__.'/custom-badge-template.svg');
}
Multi-Project Support Process multiple Clover files and merge coverage stats:
$badger = new PHPCoverageBadger();
$coverage = $badger->parseCloverFile('project1/clover.xml');
$coverage += $badger->parseCloverFile('project2/clover.xml');
$average = array_sum($coverage) / count($coverage);
$badger->generateBadge($average, 'merged-coverage.svg');
Default Output The package defaults to writing to the current directory. Always specify an explicit path to avoid overwrites.
Locale-Specific Issues
If running in a non-English locale, ensure the Clover XML uses numeric values (e.g., 100.0 instead of 100,0 for German locales).
Case Sensitivity
The Clover XML tags are case-sensitive. Ensure tags like <metrics> match exactly.
How can I help you explore Laravel packages today?