amoifr/phpquality-bundle
Symfony bundle and Docker image for PHP static code analysis. Generates reports on complexity, maintainability, coupling, architecture/SOLID layer violations, and coverage. Built as a modern replacement for the unmaintained phpmetrics/phpmetrics.
Install the Bundle:
composer require amoifr/phpquality-bundle
Register in config/bundles.php:
PhpQuality\PhpQualityBundle::class => ['all' => true],
Run First Analysis (Laravel project):
php bin/console phpquality:analyze --source=app --type=laravel
Outputs HTML report in var/reports/.
Quick Docker Alternative (no Symfony setup):
docker run --rm -v $(pwd):/project amoifr13/phpquality --source=/project/app --type=laravel
Onboarding New Developers:
--no-html for terminal summary:
php bin/console phpquality:analyze --source=app --type=laravel --no-html
Architecture Score (0-100) for project healthCCN (complexity) for critical methodsDIP violations in service layerPre-Commit Hook (fail fast):
php bin/console phpquality:analyze --source=app --type=laravel --fail-on-violation --exclude=tests
Tip: Cache results with --baseline to ignore intentional violations.
Pull Request Analysis:
php bin/console phpquality:analyze --source=app --type=laravel --coverage=coverage.xml --report-html=pr-reports
pr-reports against baselineHall of Fame for contributor-specific metricsCI Pipeline (GitHub Actions):
- name: Run PhpQuality
run: |
php bin/console phpquality:analyze --source=app --type=laravel \
--baseline=phpquality.baseline.json --fail-on-violation
Laravel-Specific:
--type=laravel to auto-exclude:
Domain)Wiring)phpquality.json:
{
"layers": {
"rules": [
{ "match": "App\\Services\\**", "layer": "Application" }
]
}
}
Test Coverage: Generate coverage first:
./vendor/bin/phpunit --coverage-clover=coverage.xml
Then analyze:
php bin/console phpquality:analyze --source=app --coverage=coverage.xml
Focus: Package Coverage for Laravel modules (e.g., App\\Http, App\\Console).
Baseline Workflow:
# Step 1: Generate baseline (run once)
php bin/console phpquality:analyze --source=app --generate-baseline=phpquality.baseline.json
# Step 2: Enforce new violations only
php bin/console phpquality:analyze --source=app --baseline=phpquality.baseline.json --fail-on-violation
Terminal Output:
php bin/console phpquality:analyze --source=app --no-html | grep -E "CCN|MI|DIP"
Use case: CI logs for critical metrics.
HTML Reports:
report/analysis.html: Dependency graph (D3.js)report/coverage.html: Package-level coverage heatmapreport/metrics.html: Method complexity breakdownJSON Export:
php bin/console phpquality:analyze --source=app --json=metrics.json
Use case: Feed metrics to monitoring tools (e.g., Prometheus).
False Positives in Laravel:
Carbon, Illuminate\Support\Collection.--type=laravel or whitelist in phpquality.json:
{
"abstractionRatio": { "ignore": ["Carbon\\*", "Illuminate\\*"] }
}
Performance:
--exclude=vendor --exclude=storage--no-html for terminal-only outputGit Blame Overhead:
--git-blame adds 10x runtime.php bin/console phpquality:analyze --source=app --git-blame --no-html
Layer Detection:
App\Domain not recognized.phpquality.json:
{
"layers": {
"rules": [
{ "match": "App\\Domain\\**", "layer": "Domain" }
]
}
}
Verbose Output:
php bin/console phpquality:analyze --source=app --verbose
Look for: Skipping [file] or Class [Class] categorized as [Layer].
Dry Run:
php bin/console phpquality:analyze --source=app --dry-run
Use case: Test phpquality.json changes without generating reports.
Isolated Analysis:
php bin/console phpquality:analyze --source=app/Services --type=php
Use case: Debug a specific module.
Custom Metrics:
PhpQuality\Analyzer\ProjectAnalyzer to add:
public function addCustomMetric(Project $project, string $name, callable $calculator) { ... }
Report Templates:
var/cache/dev/phpquality/report/ or extend:
// config/packages/phpquality.yaml
phpquality:
report:
template_path: '%kernel.project_dir%/templates/phpquality'
CLI Integration:
PhpQuality\Command\AnalyzeCommand in custom commands:
$analyzer = $this->get('phpquality.analyzer');
$result = $analyzer->analyze($sourceDir, $projectType);
Preset Extensions:
PhpQuality\Analyzer\ProjectType\ProjectType:
class CustomProjectType extends ProjectType {
protected function getLayerRules(): array {
return array_merge(parent::getLayerRules(), [
'App\\Custom\\**' => 'CustomLayer',
]);
}
}
Threshold Tuning:
Adjust in phpquality.json:
{
"thresholds": {
"ccn": 10, // Max allowed CCN
"mi": 20, // Min required MI
"dip": 0.7, // Min abstraction ratio
"layerViolations": 0 // Allow 0 layer violations
}
}
Selective Analysis:
# Only analyze critical paths
php bin/console phpquality:analyze --source=app/Http --source=app/Services
Historical Trends:
# Compare against previous baseline
php bin/console phpquality:analyze --source=app --baseline=old.baseline.json --json=diff.json
Tool: Use jq to parse diff.json for trend analysis.
Laravel Artisan Integration:
Add to app/Console/Kernel.php:
protected $commands = [
\PhpQuality\Command\AnalyzeCommand::class,
// ...
];
Use case: Run via php artisan phpquality:analyze.
How can I help you explore Laravel packages today?