Rector
| Language | PHP |
|---|---|
| Kind | tool |
| Readiness | 🟡 |
| Detector conformance | 🔴 |
| Checked | 2026-09-08, version 2.6.6 |
Rector is an automated refactoring tool built on the PHP parser and PHPStan’s type inference. Its job in a pipeline is to rewrite code, but in --dry-run mode it is a detector that reports every place a rule would change, and a custom rule can express any pattern the AST exposes, so it can host a bespoke defence whose fix is mechanical.
How it is conformant
Clause 4.1 is met: a project extends AbstractRector, declares the node types and the refactoring, and registers the class with withRules() in rector.php (custom rule). Clause 4.2 is met twice over: AbstractRectorTestCase runs a rule over a .php.inc fixture with a before and after section (custom rule), and --only="Fully\Qualified\Rule" runs one registered rule from the command line (run single rule). Clauses 5.1 to 5.4 hold: the tool runs locally, accepts a path, and prints the diff and rule name to the terminal. Clause 7.1 is met, and this is unusual: the inline @noRector annotation was removed in Rector 0.15 precisely because it hid context, and the only skip route is withSkip() in the configuration, which is visible (pull request 3148, ignoring rules or paths).
How it is not conformant
Clause 4.3 fails, and it decides the grade: the string printed with a finding is the rule’s fully qualified class name, which the clause forbids as an identifier because a rename or a namespace move changes it, and there is no field for the author to choose one. Clauses 6.1 to 6.3 fail: nothing resolves a printed name for a bespoke rule, and for bundled rules the catalogue is keyed on class name and lives on the website. Clause 7.2 has no inline route to apply to, and withSkip() takes classes and paths with no field for a reason (ignoring rules or paths). Clause 4.4 is not met and clause 6.4 was not verified.
Clause by clause
| Document | Clause | Result | Evidence |
|---|---|---|---|
| Detector | 4.1 | Yes | AbstractRector and withRules(), custom rule |
| Detector | 4.2 | Yes | AbstractRectorTestCase fixtures and --only, run single rule |
| Detector | 4.3 | No | Prints the rule class name, which the clause forbids |
| Detector | 4.4 | No | Nothing enforces an identifier |
| Detector | 5.1 | Yes | vendor/bin/rector --dry-run runs locally |
| Detector | 5.2 | Yes | Path argument accepted |
| Detector | 5.3 | Yes | Diff and rule name print to the terminal |
| Detector | 5.4 | Yes | No CI-only mode |
| Detector | 6.1 | No | No resolver keyed on a printed identifier |
| Detector | 6.2 | No | Bundled rule catalogue is on the website |
| Detector | 6.3 | No | Same |
| Detector | 6.4 | Not verified | Not found |
| Detector | 7.1 | Yes | @noRector removed; skips are configuration only, pull request 3148 |
| Detector | 7.2 | No | No inline route; withSkip() carries no reason field |
Notes for a practitioner
Rector suits a defence whose remedy is a transformation you can state exactly: write the rule, prove it with a fixture under AbstractRectorTestCase, sweep with --dry-run, and enforce with --dry-run failing the build. Treat the class name as the identifier and never move it, and put a comment beside every withSkip() entry because the configuration will not ask for one.