frosh/shopware-rector
Frosh Shopware Rector provides Rector rules to automate upgrading Shopware 6 extensions and themes to newer versions. It helps modernize plugin code, apply API changes, and reduce manual migration work so your Shopware projects stay compatible with less effort.
Add it as a dev dependency next to Rector itself — it is a ruleset, not a standalone tool, so nothing happens until Rector runs it:
composer require --dev rector/rector frosh/shopware-rector
Since 0.6.0 the package requires PHP 8.2+ (and is validated against Rector 2.6.2). Your runtime PHP can still be 8.1 — Shopware 6.4/6.5 projects do this — but the machine that runs Rector (local dev, CI job) needs 8.2. If composer require resolves an ancient 0.5.x, that's the reason; upgrade the toolchain or run Rector in a dedicated 8.2 CI job (see Gotchas about PHP_82 sets in that case).
Then look at two things in the repo before writing any config:
config/ (or sets/) in vendor/frosh/shopware-rector — the per-Shopware-version set files. These are the actual product; the PHP classes under src/Rector/ are just their implementation. ls vendor/frosh/shopware-rector/config is your first command.README table that maps a set to a Shopware minor (6.4 → 6.5 → 6.6 …). Rules are version-scoped, so the set you pick is your upgrade target.A minimal rector.php at project root:
<?php
use Rector\Config\RectorConfig;
use Rector\Set\ValueObject\SetList;
return RectorConfig::configure()
->withPaths([
__DIR__ . '/custom/plugins/Acme/src',
__DIR__ . '/custom/static-plugins/AcmeErp/src',
])
->withSkip([
__DIR__ . '/custom/plugins/*/vendor',
__DIR__ . '/custom/plugins/*/tests/Fixtures',
])
->withSets([
SetList::PHP_82,
SetList::TYPE_DECLARATION,
// Shopware rules: point at the set file for your target version
__DIR__ . '/vendor/frosh/shopware-rector/config/shopware-66.php',
]);
First use case: pick one plugin, run vendor/bin/rector process --dry-run and read the diff. Do not start with process on the whole custom/ tree — you want to learn what the rules actually rewrite before committing to a 300-file changeset.
Version-ladder upgrades. Run the sets in sequence, one Shopware minor per commit: 6.4 → 6.5, test, commit, then 6.5 → 6.6. Squashing them into one run hides which rule caused a break, and a broken intermediate state is much easier to bisect than a 2 000-line diff.
Run Rector before bumping composer.json. The rewritten code is (usually) still valid against the old platform for the APIs the rules touch, so you get an isolated, reviewable refactor commit and a separate dependency-upgrade commit. If instead you bump first, composer update failures and Rector diffs get tangled.
Scope by directory, not by rule. Shopware codebases split cleanly into Domain/, Storefront/, Administration/, Migration/. vendor/bin/rector process custom/plugins/Acme/src/Domain gives reviewable PRs and keeps the cache warm for the rest.
# per-plugin loop, one PR each
for p in custom/plugins/*/; do
vendor/bin/rector process "$p/src" --dry-run --output-format=json > "rector-$(basename $p).json"
done
CI as a ratchet, not a gate. Two jobs: a non-blocking --dry-run report on PRs (post the JSON as an artifact) and a blocking check only on changed files, so legacy files can't get worse while new code stays clean. Combine with PHPStan (frosh/phpstan-rules) and shopware-cli for build/validate — Rector fixes shape, PHPStan catches the semantics Rector can't see.
Custom rules on top. The real leverage for a big codebase is writing your own AbstractRector subclasses for in-house patterns (e.g. banning $container->get(...) in favour of constructor injection, or normalising your own event-subscriber signatures) and registering them in the same config. You inherit Rector's AST tooling and file handling for free. Note that 0.6.0 removed the rule scaffold/devenv helper script from the package — scaffold by copying an existing rule from src/Rector/ instead of expecting a generator.
Pair with a diff-only workflow. --dry-run plus git diff review is the whole quality gate. Treat Rector's output as a very fast junior developer: excellent at mechanical renames, unreliable on anything that depends on runtime container wiring.
services.xml, config.xml, routes.xml, DI service ids, shopware.yaml, and Twig templates are invisible to Rector. Class renames that also appear as strings in XML or in getSubscribedEvents() arrays will be half-migrated. Grep for old FQCNs as strings after every run: grep -rn 'Old\\ClassName' --include='*.xml' --include='*.yaml'.'Acme\Subscriber\Foo' inside an event array, EntityRepository names in Migration code, and services.xml aliases survive untouched. Rector matches nodes, not stringly-typed config.SetList::PHP_82 (or use PHP_81) so you don't introduce syntax your runtime can't parse.rector/rector and frosh/shopware-rector in composer.json so a major bump of either doesn't break your rector.php mid-upgrade (RectorConfig::configure() vs the legacy ContainerConfigurator bootstrap remains the most common copy-paste failure).CartBehavior recalculation checks ($cart->getBehavior()->isRecalculation(), etc.). Nothing in your config changes, but the output of a set you already validated does — after every package bump, re-run --dry-run on a plugin you've already migrated and check which set file the new rule lives in (git log -- vendor/frosh/shopware-rector/config). Silent behaviour changes in a set are the main reason to pin and re-read the changelog.--clear-cache (and check tmp/rector / your configured cache dir). Also confirm the set file path resolves — a wrong path in withSets() throws, but a missing rule inside a set is silently skipped.vendor everywhere, including nested custom/plugins/*/vendor. Rector happily rewrites dependencies and you lose the ability to composer update cleanly.ecs --fix (style), then commit. (The package itself moved to a newer php-cs-fixer in 0.6.0 — if you vendor its config patterns, expect style-only churn.)TYPE_DECLARATION set plus a Shopware set can interact badly (added return types on methods a later rule replaces). If output looks wrong, drop to --only on a single rule to isolate.--parallel and raise memory_limit; long runs on custom/ are much cheaper after excluding var/, vendor/, and tests fixtures.--dry-run diff that touches constructor signatures or service decoration. Those changes compile fine and fail at container build time, which is exactly where Rector's static view stops and Shopware's runtime starts. The CartBehavior rule is a good example: the API call rewrite is mechanical, but the surrounding recalculation logic is business logic — verify the cart still recalculates as expected in the storefront, not just in a unit test.withSkip() by rule class for a specific file when a rule is wrong for your case, rather than excluding the file entirely — you keep the other 20 rules working on it.How can I help you explore Laravel packages today?