PHPStan Integration with DefectDojo
PHPStan Integration with DefectDojo
PHPStan is an open source static analysis tool for PHP, created by Ondřej Mirtes and developed in the open on GitHub. It reads PHP code without running it and reports type errors, nullability problems, calls to undefined methods, dead code, and unreachable branches, at a strictness level the team chooses. Its --error-format=json option writes a JSON report that DefectDojo imports.
PHPStan Integration with DefectDojo
We added PHPStan to our PHP services to catch the class of bugs that turn into production incidents: a null where an object was expected, a string compared against an integer, a method that no longer exists. Running it in CI was easy. Tracking what it found across a dozen repositories was not. Importing PHPStan reports into DefectDojo gives each diagnostic a home on the right Asset, deduplicated by rule, file, and line, with an owner and a history that survives the next pull request.
Why PHPStan Matters
PHP gives developers a lot of freedom, and many correctness bugs only show up at runtime. PHPStan moves a large share of them to build time.
- It finds type and logic errors before tests run, including on code paths tests never exercise.
- Rule levels let a team adopt it gradually on legacy code and tighten the level over time.
- Each diagnostic carries a stable rule identifier such as
missingType.iterableValue, which makes it possible to track or baseline one rule at a time. - Correctness defects are often where security defects start, for example when unvalidated types flow into a sensitive call.
- PHPStan's own baseline file hides known errors from the CLI, but it does not tell anyone who owns them or when they will be fixed.
Advantages of This Integration
What we got by sending PHPStan output through DefectDojo:
- Stable deduplication. DefectDojo hashes PHPStan findings on the rule identifier, file path, and line, so rerunning the analysis on unchanged code does not create new findings.
- Change tracking between builds. Reimporting into the same Test mitigates diagnostics that were fixed, adds new ones, and reactivates any that came back.
- Meaningful severity without guessing. PHPStan has no severity scale, so the parser derives one from what PHPStan does report: analysis failures are High, non-ignorable diagnostics are Medium, and the ordinary ignorable bulk is Low.
- Incomplete scans become visible. When PHPStan cannot finish, for example on a missing configuration file, that error becomes a High finding, so a broken analysis is not mistaken for a clean one.
- One queue for code issues. PHPStan findings sit next to dependency and secret scanning results on the same Asset, and can be assigned, risk-accepted, or pushed to Jira.
How This Integration Works
DefectDojo imports PHPStan output with the PHPStan Scan scan type, which expects the JSON written by --error-format=json.
1. Run PHPStan with JSON output. From the project root, using your existing phpstan.neon configuration:
vendor/bin/phpstan analyse --error-format=json --no-progress > phpstan.json
--no-progress turns off the progress bar, which only adds noise to a CI log. PHPStan exits with a non-zero code when it reports errors, so in a pipeline, allow the step to continue and let DefectDojo and your release gate decide what blocks.
2. Import the file. In the UI, open the Engagement, choose Import Scan Results, select PHPStan Scan, and upload the file. To automate it, use the API in Community Edition or DefectDojo Pro:
curl "https://YOUR_INSTANCE/api/v2/import-scan/"
-H "Authorization: Token $DD_API_TOKEN"
-F "scan_type=PHPStan Scan"
-F "file=@phpstan.json"
-F "product_name=billing-service"
-F "engagement_name=CI"
-F "auto_create_context=true"
DefectDojo Pro users can do the same with Universal Importer:
universal-importer import
--defectdojo-url "https://YOUR_INSTANCE.cloud.defectdojo.com/"
--scan-type "PHPStan Scan"
--report-path "./phpstan.json"
--product-name "billing-service"
--engagement-name "CI"
--auto-create-context
3. Reimport on each build. Send later reports to /api/v2/reimport-scan/ for the same Test, so fixed diagnostics are closed automatically and the Test reflects the current branch.
The parser walks the files map in the report, creating one Finding per diagnostic, and then turns each entry in the top level errors list into an analysis error Finding.
Data Granularity: What Gets Imported
| DefectDojo Field | Source in PHPStan Report | Notes |
|---|---|---|
| Title | identifier and message |
Formatted as identifier: message; message alone if no identifier |
| Severity | ignorable flag, or top level error |
Top level error High, ignorable: false Medium, ignorable: true Low |
| Description | message, identifier, ignorable, tip |
PHPStan's tip on how to resolve the diagnostic is included when present |
| File Path | Key of the files map |
The analysed file |
| Line | line |
Line PHPStan reported |
| Vuln ID from Tool | identifier |
The PHPStan rule identifier |
| Analysis errors | Top level errors[] |
Titled "PHPStan analysis error", with a note that part of the code may not have been analysed |
| CWE | Not set | PHPStan reports no CWE values |
| Finding type | Static | All PHPStan findings are static |
| Deduplication | Hashcode | Vuln ID from tool, file path, line |
Use Cases
In a CI/CD pipeline: Every merge to main runs PHPStan and reimports the report into a Test for the repository. Developers see what their change added or removed, and the security team gets the trend across all PHP services without opening CI logs.
When adopting PHPStan on legacy code: A team starting at a low level imports the first report to measure the backlog, assigns owners by directory, and raises the level once the Low findings are under control. DefectDojo shows the count shrinking over time.
For catching broken analysis: A configuration change breaks autoloading and PHPStan can no longer analyse part of the codebase. The top level error arrives as a High finding, which alerts the team instead of silently reporting fewer issues.
For rule-focused cleanup: A lead filters findings by rule identifier, for example every identical.alwaysFalse diagnostic, and turns them into one Jira ticket per service.
Operational Tips
- Raise priority by rule, not by level. Severity comes from analysis metadata, so if a specific rule matters more to you, filter on its identifier and adjust severity during triage.
- Keep using PHPStan's baseline if you rely on it, but know that baselined diagnostics do not appear in the report. Importing without the baseline gives DefectDojo the full picture.
- Top level analysis errors have no identifier, file, or line, so they share the same hash values. Treat any analysis error finding as a signal to fix the scan configuration first.
- Use one Test per repository and branch stream and reimport into it, so mitigated findings reflect real fixes rather than a new Test each run.
- Set
minimum_severity=Mediumon import if you only want non-ignorable diagnostics and analysis failures in DefectDojo at first. - Tag imports with the PHPStan level you ran (for example
tags=phpstan-level-6) so changes in finding counts can be traced to level changes.