sylius/sylius-rector
Sylius Rector provides Rector rules for automating Sylius upgrades and code modernization. It helps refactor deprecated APIs, apply version-specific migrations, and keep PHP/Sylius projects compatible with less manual work.
sylius/sylius-rector is a Rector rule set that automates the mechanical parts of a Sylius upgrade — renamed services, moved classes, dropped/renamed methods, signature changes between Sylius minor/major versions. Treat it less as an application dependency and more as a one-shot upgrade tool you run on a branch.
v3.9.0 changes the entry point. The set provider was replaced with Composer version-bound rule configurations. If you are following older blog posts or snippets that use SyliusSetList::SYLIUS_1_12 / SYLIUS_1_13, stop and read the current README first. The package now keys rule configs to installed Composer versions, so the lock file matters before Rector even runs.
Install
composer require --dev sylius/sylius-rector rector/rector
Keep both in require-dev — this package should never end up in production autoload.
Where to look first
README.md and CHANGELOG.md for v3.9.0. The set-provider replacement is the first thing to understand; do not copy pre-3.9 set names blindly.config/ for the version-bound rule configurations. These are now the source of truth for which rules apply to which sylius/sylius constraint.composer.lock before running anything. The package resolves rules against the installed Sylius version, so a stale lock file can select the wrong rule set.AddIndexToClassExtendingTypeRector and the LiveOnePageCheckoutPlugin rule — if your project uses those areas; otherwise they are just additional rewrites to review in the diff.First use case
Upgrade one Sylius minor version at a time. In v3.9.0 the README snippet is the authoritative example; paste it verbatim rather than reconstructing old set constants. It will look roughly like this:
// rector-upgrade.php — use the current v3.9.0 set/config name from README
use Rector\Config\RectorConfig;
return RectorConfig::configure()
->withPaths([__DIR__ . '/src', __DIR__ . '/tests'])
->withSets([
// Sylius Rector set/config bound to your installed Sylius version.
// Confirm the exact name/path in README for v3.9.0.
]);
Then:
composer update sylius/sylius:1.12.* # make the lock file match the target
vendor/bin/rector process --dry-run # review the diff
vendor/bin/rector process # apply
Commit on a dedicated upgrade/sylius-1.12 branch, run PHPUnit/Behat, and only then keep the bumped Sylius constraint.
The upgrade ladder still applies, but Composer drives the rules. Sylius upgrades are sequential. Do not jump from 1.10 to 1.13 in one Rector run. The difference in v3.9.0 is that you no longer manually swap set constants for every step; the package binds rules to Composer versions. The safe workflow is still:
composer update sylius/sylius:1.11.* → run Rector → test → commit
composer update sylius/sylius:1.12.* → run Rector → test → commit
The version-bound config is an improvement over the old manual ladder, but it makes composer.lock part of the Rector input. If the lock file is stale, the selected rules are stale too.
Use a dedicated upgrade config. Don't pollute your steady-state rector.php with Sylius sets — they are version-scoped and will go stale. Keep rector-upgrade.php committed only while the upgrade branch is open:
vendor/bin/rector process --config rector-upgrade.php
Delete it after the merge. This avoids the classic failure where someone re-runs the main config months later and re-applies an old rule to already-migrated code.
Composer script + CI dry-run. Wire it up so CI catches drift:
"scripts": {
"rector:upgrade": "rector process --config rector-upgrade.php"
}
Add a non-blocking CI job that runs --dry-run on PRs touching composer.json / composer.lock. With v3.9.0's Composer-bound configs, this is even more valuable: a Sylius bump without the matching Rector pass now leaves the rule selection out of sync with the code.
Run Rector before formatters. Run Rector before php-cs-fixer / phpcs, never after. Rector emits code that is syntactically valid but not necessarily style-conformant; the formatter then cleans it in the same commit. Running them in reverse means you fight formatting diffs inside the Rector output.
Pair with Symfony sets. Sylius is Symfony-based, so the same rector.php usually needs Rector\Symfony\Set\SymfonySetList alongside the Sylius set. Keep both in withSets() and let Rector resolve ordering — but review the diff, because both can touch the same file.
Custom rule authoring (extension point). When your project has its own deprecated APIs (custom promotions, order processors, resource classes), write a small AbstractRector rule modeled on the ones in this package and add it to withRules(). Copying the shape of a Sylius rule — getNodeTypes(), refactor(), __construct with NodeTypeResolver/NodeFactory — is the fastest way to learn the Rector API. Put project-specific rules in tools/rector/src/ and autoload them, not in src/.
Treat new v3.9.0 rules as review targets, not automatic wins. AddIndexToClassExtendingTypeRector and the LiveOnePageCheckoutPlugin rule are incremental improvements, but they can still produce diffs in resource/plugin-heavy projects. If you use LiveOnePageCheckoutPlugin, test the checkout flow manually after applying the rule.
The v3.9.0 set-provider change is the main breaking change. The old SyliusSetList::SYLIUS_1_12 style entry point may no longer be the documented API. If your rector.php references a removed class/constant, Rector will fatal before processing any files. Update the config from the current README first; don't mix old set names with the new Composer version-bound configs.
Version skew is still the #1 trap — now enforced by Composer. Running Rector against a codebase whose composer.lock does not match the target Sylius version can select the wrong rules. Always run sets against the matching lock file. If you use a CI matrix, each job must use its own installed Sylius version; don't share a cached lock file across matrix legs.
Bump the constraint before running Rector. Rector will rewrite your code to the target API while composer.json still pins the old version if you run it too early. The tests then fail confusingly because static analysis resolves against the wrong API. Sequence: bump constraint → composer update → run Rector → test.
--dry-run before every apply, unconditionally. Rector is not idempotent across rule sets and some rules delete code. On a dirty working tree, rector process can also silently include your unrelated edits in the diff. Commit or stash first.
Exclude vendor/, var/, and generated code. Sylius apps contain a lot of generated proxies, cached container files, and entity repositories. Add:
->withSkip([__DIR__ . '/var', __DIR__ . '/src/Entity', ...])
Skipping src/Entity matters — Rector's docblock/type rewrites on ORM entities can change mapping semantics in ways tests won't catch until a fixture loads.
Cache invalidation. Rector caches in .rector/cache. If a rule appears to do nothing, or a changed file isn't picked up, rm -rf .rector/cache (or --clear-cache) before assuming the rule is broken. Conversely, add .rector/ to .gitignore so the cache doesn't leak into upgrade PRs.
Rules stop at syntax, not semantics. A renamed method gets rewritten, but behavioural changes — a service now returns a different type, a promotion action fires at a different lifecycle point — are invisible to Rector. Budget manual review time proportional to the number of service decorators and state machine customizations in the project. Those are where a Sylius upgrade actually hurts.
Debugging a single rule. When a diff is wrong, isolate it:
vendor/bin/rector process src/Path/To/File.php --only='Sylius\SyliusRector\Rector\...\SomeRule'
--only plus a single file is far faster than bisecting a full-project run. Add -vvv to see which files Rector decided were candidates.
Deprecation warnings are guidance, not blockers. Some rules target classes the current Sylius still ships with @deprecated annotations. Applying the rule may move you to a replacement that's not fully documented yet. If CI goes red on a rule you don't understand, comment out that one rule (not the whole set), leave a // TODO: SyliusRector <rule> in the PR description, and revisit — the package is small and low-traffic, so upstream issue turnaround is not something to plan a sprint around.
Keep the tool pinned. With a small release cadence, an unpinned range can move under you. Pin an exact version in composer.json for the duration of an upgrade branch, and re-evaluate the pin — don't just widen it — on your next upgrade cycle. This is especially important for v3.9.0 because the set-provider change alters how configs are resolved.
How can I help you explore Laravel packages today?