rector/rector-doctrine
Rector Doctrine provides Rector rules to automate refactoring and upgrading Doctrine ORM/DBAL code in PHP projects. It helps modernize annotations, mappings, queries, and legacy APIs with safe, repeatable code transformations.
Install the package as a dev dependency alongside Rector, then register a Doctrine set in rector.php:
composer require --dev rector/rector rector/rector-doctrine
<?php
// rector.php
use Rector\Config\RectorConfig;
use Rector\Doctrine\Set\DoctrineSetList;
return RectorConfig::configure()
->withPaths([__DIR__ . '/src/Entity'])
->withSets([
DoctrineSetList::ANNOTATIONS_TO_ATTRIBUTES,
]);
If you are on an older Rector that still uses the closure form:
use Rector\Config\RectorConfig;
use Rector\Doctrine\Set\DoctrineSetList;
return static function (RectorConfig $rectorConfig): void {
$rectorConfig->paths([__DIR__ . '/src/Entity']);
$rectorConfig->sets([DoctrineSetList::ANNOTATIONS_TO_ATTRIBUTES]);
};
Where to look first: open Rector\Doctrine\Set\DoctrineSetList in vendor/rector/rector-doctrine/src/Set/. The class constants are the public API—every set you can safely reference lives there (DOCTRINE_25, DOCTRINE_26, DOCTRINE_27, DOCTRINE_CODE_QUALITY, ANNOTATIONS_TO_ATTRIBUTES, GEDMO_ANNOTATIONS_TO_ATTRIBUTES, TYPED_COLLECTIONS). Skim config/sets/ to see which rules each set composes; that is your fastest way to know what a set will actually change.
First use case: migrate annotation mapping to PHP 8 attributes on an existing entity folder. Run dry first:
vendor/bin/rector process src/Entity --dry-run
Review the proposed diffs, then rerun without --dry-run on a dedicated branch. This is the highest-value, lowest-risk win the package offers and the reason most teams install it.
1. Annotation → attribute migration, in isolation. Run this set alone, in its own commit. Mixing it with code-quality or dead-code sets makes the diff unreviewable. Restrict withPaths() to entity directories only; the annotation rules are not useful on services, DTOs, or migrations.
->withSets([DoctrineSetList::ANNOTATIONS_TO_ATTRIBUTES])
->withSkip([
\Rector\Doctrine\CodeQuality\Rector\Class_\SomeRuleYouDontWantRector::class,
])
2. Staged version upgrades. Treat DOCTRINE_25, DOCTRINE_26, and DOCTRINE_27 as an ordered ladder, not a single set. Run one, commit, run tests, then the next. This surfaces a Doctrine upgrade regression against a known small diff instead of a giant one.
->withSets([
DoctrineSetList::DOCTRINE_25,
DoctrineSetList::DOCTRINE_26,
DoctrineSetList::DOCTRINE_27,
])
Order matters: if you are also converting annotations to attributes, run ANNOTATIONS_TO_ATTRIBUTES first, commit, then run the version sets. Attribute mapping is the prerequisite for several Doctrine 3-era expectations, and it keeps each step independently revertible.
3. Code-quality set for entity hygiene. DOCTRINE_CODE_QUALITY normalizes common entity mistakes—mismatched default values on properties ($id = false on an integer id), weak collection docblocks, and other patterns that cause subtle hydration or typing bugs. Run it on entities as a periodic cleanup rather than on every CI build.
4. Typed collections rollout. TYPED_COLLECTIONS adds generic-native docblocks and typed Collection properties (@var Collection<int, Item> / Collection). Use it to enable PHPStan/Rector type analysis on aggregates. Expect to initialize properties (= new ArrayCollection()) manually afterward; the rule narrows types but should not be trusted to restructure constructors.
5. Gedmo and extension mappings. If you use gedmo/doctrine-extensions, GEDMO_ANNOTATIONS_TO_ATTRIBUTES converts @Gedmo\Timestampable, @Gedmo\Slug, etc. Run it after the ORM annotation conversion, and remember custom bundles (JMS serializer, API Platform) have their own mappings—this package does not convert those.
6. CI guardrail. A rector process --dry-run job scoped to entities catches regressions where a developer reintroduces an old Doctrine pattern (or a merge conflict reverts an attribute). Keep the process step and the dry-run step on the same config file to avoid drift.
7. Monorepo scoping. In a multi-module repo, create one rector.php per module with only the Doctrine paths for that module, or use withPaths() with explicit entity globs. Do not point the Doctrine sets at the whole src/—reflection and annotation parsing across thousands of unrelated files is slow and produces noise.
composer.json before running ANNOTATIONS_TO_ATTRIBUTES; a half-converted codebase on an older ORM will fail at runtime, not at Rector time.doctrine/annotations installed during the migration. The annotation rules parse existing annotations; removing the annotations package before you have finished converting leaves you unable to run the conversion again on remaining files.vendor/bin/rector process ... --clear-cache. Rector's cache keys on config, but editing a set list mid-run is a common source of "why isn't it applying?"use Doctrine\ORM\Mapping as ORM; and emits #[ORM\Column(...)]. If your project uses use Doctrine\ORM\Mapping\Column;, either adjust with withImportNames() settings or do a follow-up cleanup. Mixed import styles make the diff noisy.withRules([...]) list mixing Doctrine rules with your own, verify precedence—annotation-to-attribute rules should precede any rule that inspects mapping metadata.withSkip() for these rules requires the full rule class name (e.g. Rector\Doctrine\...\Rector), not the set name. Grab it from the dry-run output.@var Collection<int, Foo> can unlock strict PHPStan rules that then flag existing untyped usages. Roll it out module-by-module and pair it with a static-analysis run, not just a test run.ObjectManager interface changes), expect to write custom Rector rules—this package's coverage is intentionally narrow.AbstractRector subclass for any mapping pattern the package misses (custom annotations, project-specific base entities), and register it in withRules(). Keep it in tools/rector/ so it is versioned with the project rather than the vendor tree.How can I help you explore Laravel packages today?