Two tools dominate the PHP migration workflow and they are constantly mistaken for competitors. They are not. PHPCompatibility is a set of sniffs that answers "what will break on this version"; Rector is a refactoring engine that answers "how do I change it". A team that picks one and expects the other half from it usually gives up halfway through the migration.
The short answer
Use PHPCompatibility to find the incompatibilities and Rector to apply the fixes. They complement each other: the sniff produces a list of locations and the version each one affects, the refactoring engine produces the diff. What neither tool does on its own is show you the result of the change as a single report that a colleague or a client can read, and neither one verifies that the upgraded code still behaves the same way.
That last part is the reason a separate verification step exists. A clean scan means the known incompatibilities are gone. It does not mean the application still works.
What each tool actually does
PHPCompatibility
PHPCompatibility is a PHP_CodeSniffer standard. You run it as a coding standard check and it reports, per file and line, the constructs that are deprecated or removed in the PHP version you configure. Its output is a list of findings, which is exactly what you want to have in front of a team before the migration starts.
Its strength is breadth and precision on the version rules: it knows the exact release where a function was deprecated, and it knows the target version's own deprecations, including the implicit nullable parameter rule that only matters on PHP 8.4.
Rector
Rector applies changes to your code. Its upgrade rule sets (SetList::PHP_74,
SetList::PHP_80 and so on) know how to transform the deprecated constructs into
their modern equivalents, and they can apply hundreds of them across a codebase in one run.
Its strength is the transformation work. It also has a dry-run mode that prints the diff without touching the files, which is the safe way to introduce it to a team.
Side by side
| PHPCompatibility | Rector | |
|---|---|---|
| Purpose | Detect incompatibilities | Apply transformations |
| Output | Findings per file and line | Modified files and a diff |
| Needs a test suite | No | Strongly recommended |
| Config surface | phpcs.xml with the standard and the test version | rector.php with the rule sets and the paths |
| Typical usage | CI check, or a one-off report before the migration | A local run, reviewed as a diff, then committed |
| What it does not do | Change anything | Decide the findings that need a human |
Which workflow fits your team
If you are starting from nothing
Run PHPCompatibility first, with the version you intend to move to. The report tells you the size of the job before anyone commits to a date. Most teams discover that the two biggest buckets are the removals and the null handling, and that everything else is a long tail.
If the scan is already clean but the code still looks old
The scan being clean means no known incompatibility is left. It does not mean the code is modern. Rector's non-upgrade rule sets handle style and modernisations, which is a different job with a different risk profile.
If you are upgrading several projects
The part that scales badly is the hand-off between the two tools: the sniff writes a report, the fixer runs, someone reads the diff, and then the result has to be explained to a client. A tool that keeps the scan, the diff and the summary in one place removes that hand-off. That is the argument for a single workspace over two command line tools, and it is the reason php2shell puts the three in one flow.
Using both together
The standard setup for a large codebase runs both. The order is the same as the order of the work:
- Scan. PHPCompatibility with the target version produces the list of findings.
- Triage. Group the findings by rule. The rules that map onto a safe transformation go to the fixer, the rest become tasks.
- Dry-run. Rector runs in dry-run mode so you can read the diff before anything is written.
- Apply in batches. One rule group per commit, with the test suite running after each one.
- Report. The findings that were fixed automatically and the ones that need a developer are written down, because the second list is what the project actually owes you.
Putting the checks in CI
Both tools belong in the pipeline, in different roles:
# every push: fail the build on a new incompatibility
vendor/bin/phpcs --standard=PHPCompatibility --runtime-set testVersion 8.3- --report=summary src/
# nightly: open a pull request with the mechanical upgrades applied
vendor/bin/rector process --dry-run --config=rector.php src/
The first command is a gate: it is fast, it has no side effects, and it stops a new incompatibility from being merged. The second is a scheduled job, because it produces a large diff that a human has to look at, and a diff nobody looks at is a diff nobody merges.
The step neither tool does
Neither PHPCompatibility nor Rector tells you whether the migrated application still works. They tell you about the source code; only the test suite tells you about the behaviour.
So the workflow is not complete without running PHPUnit on the new runtime and reading the result. The pass rate is the metric, and the list of failing test cases is the work list for the next batch. If the suite was already failing before the upgrade, record the rate first: afterwards, the same rate with a different set of failures is progress and a lower rate is a regression. The verification article covers the details.
Frequently asked questions
Should I use PHPCompatibility, Rector or both?
PHPCompatibility answers 'what will break' and Rector answers 'how do I fix it'. Running both is the standard setup for a large codebase: the sniff for the report, the tool for the diff. A tool that does both in one flow, with the report and the diff in the same place, saves the handover.
Does Rector fix everything PHPCompatibility finds?
No. Rector covers the transformations that can be applied safely by a program. Findings such as a removed function need a human decision, and they still have to be reported - which is why a fixer alone is not enough.
Can I run these tools in CI?
Yes. The compatibility rules can be a coding standard check in CI, and the fixer can run in a nightly job that opens a pull request. The verification step - running the test suite and reading the pass rate - belongs to the same pipeline.
Where to go next
- How to upgrade PHP 7.4 to 8.3 — the full step-by-step migration plan.
- Deprecated PHP functions that break PHP 8 — the removal and deprecation checklist.
- Pricing — the scanner is free, the fixer and the verification are in the licence.
- The FAQ — plans, credits, licences and how your code is handled.