A CI quality gate for method-level change risk in PHP projects.
Complex code with low test coverage is risky to change. The CRAP score captures this pattern in a single number. crap-check turns that signal into a CI gate: if any method exceeds the threshold, the build fails.
This matters in any workflow where code can be generated, refactored, or expanded quickly — including AI-assisted development — because static analysis and basic tests can pass while change risk quietly accumulates at method level.
CRAP (Change Risk Anti-Patterns) is a metric that combines cyclomatic complexity and test coverage. A method scores higher when it is both complex and poorly tested. The formula is:
CRAP(m) = comp(m)² × (1 - cov(m)/100)³ + comp(m)
A low CRAP score means the method is either simple, well-tested, or both. A high score is a signal worth investigating — not necessarily a hard blocker, but a starting point for review.
- Reads a Crap4J XML report generated by PHPUnit
- Compares each method's CRAP score against a configurable threshold
- Prints the violations sorted by severity
- Exits with a non-zero code so CI fails when the threshold is exceeded
- Diagnoses environment and PHPUnit configuration (
doctorcommand)
- Generate code coverage (that is PHPUnit's job, with PCOV or Xdebug)
- Replace tools like Codecov for overall coverage tracking
- Apply per-method or per-path ignores (planned for a future release)
- Read Clover or other coverage formats
composer require --dev lvandi/php-crap-checker# 1. Generate the Crap4J report with PHPUnit
php -d pcov.enabled=1 vendor/bin/phpunit --coverage-crap4j build/crap4j.xml
# 2. Check the threshold
vendor/bin/crap-check check build/crap4j.xml --threshold=30The report argument defaults to build/crap4j.xml and --threshold defaults to 30.
| Option | Default | Description |
|---|---|---|
report |
build/crap4j.xml |
Path to the Crap4J XML report |
--threshold |
30 |
Maximum allowed CRAP score (exclusive) |
--max-violations |
(none) | Maximum number of violations before CI fails |
--max-age |
(none) | Maximum report age; accepts minutes (60), or duration strings (30m, 2h) |
--format |
text |
Output format (text, json) |
- name: Run PHPUnit with Crap4J coverage
run: php -d pcov.enabled=1 vendor/bin/phpunit --coverage-crap4j build/crap4j.xml
- name: Check CRAP threshold
run: vendor/bin/crap-check check build/crap4j.xml --threshold=30Full example with PHP matrix:
jobs:
ci:
runs-on: ubuntu-latest
strategy:
matrix:
php: ["8.3", "8.4"]
steps:
- uses: actions/checkout@v4
- uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php }}
extensions: pcov
coverage: pcov
- run: composer install --no-interaction --prefer-dist
- run: php -d pcov.enabled=1 vendor/bin/phpunit --coverage-crap4j build/crap4j.xml
- run: vendor/bin/crap-check check build/crap4j.xml --threshold=30php-crap-checker and Codecov serve different purposes and work well together:
- name: Run PHPUnit with coverage
run: |
php -d pcov.enabled=1 vendor/bin/phpunit \
--coverage-clover build/clover.xml \
--coverage-crap4j build/crap4j.xml
- name: Check CRAP threshold
run: vendor/bin/crap-check check build/crap4j.xml --threshold=30
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v5
with:
files: build/clover.xml
fail_ci_if_error: trueCodecov tracks overall and patch coverage. crap-check enforces a per-method CRAP threshold. They complement each other.
| Code | Meaning |
|---|---|
0 |
OK — no violations |
1 |
CRAP threshold exceeded |
2 |
Invalid input (e.g. non-numeric threshold) |
3 |
Report file not found or not readable |
4 |
Invalid XML in report |
5 |
Report valid but contains no methods |
CRAP threshold OK. Max allowed: 30
Analyzed methods: 128
Violations: 0
CRAP threshold exceeded. Max allowed: 30
3 violations found:
1) App\Legacy\ReportGenerator::generate()
File: src/Legacy/ReportGenerator.php:10
CRAP: 72.00
Complexity: 18
Coverage: 0.00%
2) App\Service\OrderImporter::import()
File: src/Service/OrderImporter.php:42
CRAP: 46.23
Complexity: 12
Coverage: 41.60%
3) App\Service\ShippingCalculator::calculate()
File: src/Service/ShippingCalculator.php:88
CRAP: 35.10
Complexity: 9
Coverage: 22.00%
Report not found: build/crap4j.xml
Generate it with:
php -d pcov.enabled=1 vendor/bin/phpunit --coverage-crap4j build/crap4j.xml
PCOV and Xdebug are not required by this package. They are required by PHPUnit to generate the Crap4J report. Once the report exists, crap-check reads the XML and needs no coverage driver.
In CI, install PCOV (faster) or Xdebug only in the step that generates coverage:
- uses: shivammathur/setup-php@v2
with:
php-version: "8.3"
extensions: pcov
coverage: pcovIf you already have a report from a previous step or artifact, you can run crap-check without any coverage extension.
A high CRAP score on a method is a signal, not a verdict. Before raising the threshold or suppressing the check, consider:
- Add tests: if the method is complex but untested, coverage will bring the score down
- Reduce complexity: extract helper methods or simplify branching logic
- Accept and document: some methods are inherently complex and well-understood — a future baseline feature will allow explicit exceptions
Mechanically writing tests just to lower the number defeats the purpose. Use the score to guide meaningful improvement.
If your codebase already has many violations, a zero-tolerance threshold from day one is counterproductive. See docs/ADOPTING.md for gradual adoption strategies: start permissive and tighten over time, cap violations with --max-violations, and how to talk to your team about the metric.
Future releases may add:
- baseline support (snapshot a list of known violations, block only new ones)
--fail-on=newand--fail-on=worsened- ignore rules for paths and specific methods
- GitHub Actions annotations
- PHP
>=8.3 ext-simplexmlsymfony/console^7.0
PHP runs inside Docker. Build the image once (uses your host UID/GID to avoid file permission issues):
make build
make installCommon commands:
make test # PHPUnit
make qa # PHPUnit + PHPStan (CI gate)
make stan # PHPStan level 9
make cs-fix # PHP CS Fixer
make infection # Mutation testingContributions are welcome. See CONTRIBUTING.md for guidelines on opening issues and submitting pull requests.
To report a security vulnerability, follow the process described in SECURITY.md.
This project is released under the MIT License.