Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Rector Symfony Laravel Package

rector/rector-symfony

Rector rules for Symfony that automate framework upgrades and code modernization. Apply automated refactorings across Symfony projects, including deprecation fixes and version migrations, using the Rector tool.

View on GitHub
Deep Wiki
Context7

Getting Started

Reality check first: this package is a set of Rector rules for upgrading and modernising Symfony code. In a pure Laravel app it will find very little to do, because Laravel's own conventions (facades, app(), Eloquent, Blade) aren't Symfony idioms. It becomes genuinely useful in four situations:

  1. You maintain a package that supports both Laravel and Symfony (very common: notification channels, queue bridges, HTTP clients).
  2. You're touching a Symfony component directly — HttpFoundation, Console, Process, Finder, DomCrawler, Mime, Translation — which Laravel ships and you may consume.
  3. You're migrating an app or bundle onto Laravel from Symfony (or the reverse) and want the Symfony side modernised before/after.
  4. You're doing a Doctrine-based or attribute-heavy codebase that happens to sit next to Laravel.

Minimal install

composer require --dev rector/rector rector/rector-symfony
<?php
// rector.php
use Rector\Config\RectorConfig;
use Rector\Symfony\Set\SymfonySetList;

return RectorConfig::configure()
    ->withPaths([__DIR__ . '/src', __DIR__ . '/tests'])
    ->withSets([
        SymfonySetList::SYMFONY_CODE_QUALITY,
    ]);

Then, always:

vendor/bin/rector process --dry-run   # read the diff first
vendor/bin/rector process             # apply

Where to look first

  • vendor/rector/rector-symfony/config/sets/ — every set is a plain PHP array of rule classes. Reading the set file is faster than reading docs and tells you exactly what will change.
  • SymfonySetList constants — the canonical list of what you can opt into. Upgrade sets are versioned (SYMFONY_60, SYMFONY_64, SYMFONY_70, SYMFONY_71…); there are also non-versioned sets like SYMFONY_CODE_QUALITY and SYMFONY_CONSTRUCTOR_INJECTION.
  • SymfonySetList::ANNOTATIONS_TO_ATTRIBUTES if you have any Doctrine annotation code.

First use case

Start with SYMFONY_CODE_QUALITY only. It is a safe, mostly-semantics-preserving cleanup set that removes dead container calls, simplifies getParameter() usage, and normalises a few Symfony idioms. If that produces zero diff on your Laravel codebase — which is the likely outcome — you've confirmed the package's cost/benefit for the project in about two minutes, and you can keep it in rector.php purely for the day a Symfony-flavoured dependency lands in src/.


Implementation Patterns

Pattern 1: Layer sets by blast radius

Don't dump everything into one withSets(). Order and separation matter, because a later set can rewrite code an earlier one just produced.

return RectorConfig::configure()
    ->withPaths([__DIR__ . '/src'])
    ->withSets([
        // 1. Symfony idioms / dead code — safe
        SymfonySetList::SYMFONY_CODE_QUALITY,

        // 2. Constructor injection — opinionated, review the diff
        SymfonySetList::SYMFONY_CONSTRUCTOR_INJECTION,

        // 3. Version upgrades — only after 1 & 2 are clean
        SymfonySetList::SYMFONY_70,
    ]);

Run stage 1, commit. Run stage 2, commit. Run stage 3, commit. Three small PRs beat one 400-file PR every time.

Pattern 2: CI as a drift detector, not a fixer

For a Laravel-first repo, the highest-value use is a non-blocking CI job that fails when new Symfony-idiom debt appears:

- run: vendor/bin/rector process --dry-run --no-progress-bar

If it's noisy on day one (--dry-run exits non-zero on any diff), gate it to the directories that actually contain Symfony-adjacent code:

->withPaths([__DIR__ . '/src/Support', __DIR__ . '/packages'])

Pattern 3: Target rules, not sets, for surgical work

When you're modernising one specific thing — say, converting $container->get() to constructor injection in a single service class — skip the set and name the rule:

->withRules([
    \Rector\Symfony\Rector\Class_\ConstructorInjectionRector::class, // illustrative
])

and combine with path scoping:

vendor/bin/rector process src/Support/Http --dry-run

This is how you make the package earn its keep in a Laravel repo: a dozen narrow, deliberate invocations rather than one broad sweep.

Pattern 4: Pair it with rector-laravel

The two packages are complements, not competitors. In a hybrid or package repo:

->withSets([
    \Rector\Laravel\Set\LaravelSetList::LARAVEL_110,
    SymfonySetList::SYMFONY_CODE_QUALITY,
])

Rector's rule engine walks each file once per set; having both registered means a single rector process run covers both halves of a shared codebase. Keep the PHP level consistent across both (->withPhpSets()), because Symfony upgrade rules sometimes emit code that needs ->withPhp82() (readonly classes, never return types) to stay valid.

Pattern 5: The migration-bridge workflow

If you're porting Symfony code into Laravel:

  1. Run the Symfony upgrade sets against the source Symfony project first, targeting the Symfony version whose components Laravel currently pins.
  2. Commit that as a standalone "modernise upstream" commit.
  3. Then do the framework port.

Doing it in this order keeps the port diff readable — reviewers see "Symfony 5.4 → 6.4" and "Symfony → Laravel" as two distinct changes instead of one tangled mess.

Pattern 6: Per-package execution in a monorepo

for pkg in packages/*/; do
  vendor/bin/rector process "$pkg/src" --dry-run --clear-cache || echo "diff in $pkg"
done

--clear-cache between runs is cheap insurance; Rector's cache is keyed on file hashes and config, and stale entries after a config change are a classic source of "why isn't my rule firing."


Gotchas and Tips

Version alignment is non-negotiable

rector-symfony majors track Rector majors. Rector 1.x → rector-symfony 1.x; Rector 2.x → rector-symfony 2.x. If you see rules silently doing nothing or a Class not found on a set constant, check this pairing before anything else. Pin both in composer.json and bump them together.

Upgrade sets assume you're dropping the old version

This is the biggest foot-gun for a Laravel developer. SYMFONY_60 is not "make my code nicer" — it's "I no longer support Symfony 5.x, so remove the compatibility shims." It will happily delete branches guarded by method_exists() or Kernel::VERSION checks that you still need.

Laravel pins Symfony components at whatever the framework's composer.json allows. You are not the one deciding the Symfony version — Laravel is. So the version-gated upgrade sets are largely inert or actively wrong in an app; reserve them for packages where you control the constraint.

Practical check before running any SYMFONY_7x set:

composer show symfony/http-foundation | grep versions

Type inference, not text matching

Rector rules are type-aware. If a class isn't autoloadable, or a return type is mixed, the rule quietly skips it. Symptoms: "the rule I enabled did nothing."

Fixes, in order:

  1. --debug to see per-file, per-rule decisions.
  2. Ensure --autoload-file (or the default vendor/autoload.php) resolves your classes.
  3. Narrow to one file: vendor/bin/rector process src/Foo.php --dry-run --debug.

--only-rule for surgical debugging

When a large set produces one weird diff, isolate it:

vendor/bin/rector process src/Foo.php --dry-run --only-rule='Rector\Symfony\...\SomeRector'

Nine times out of ten the culprit is a rule that misreads a dynamic call as a static one.

withSkip() beats reverting

Rather than hand-reverting a diff after every run, encode the exception:

->withSkip([
    \Rector\Symfony\Rector\Class_\SomeRector::class => [
        __DIR__ . '/src/Legacy/Bridge.php',
    ],
    // or skip by rule everywhere
    \Rector\Symfony\Rector\MethodCall\WhateverRector::class,
])

This survives rebases and keeps CI honest. Document why in a comment — future-you will not remember.

Imports get rewritten; run your formatter after

Rector adds FQCNs inline unless ->withImportNames() is set. Always finish with Pint or PHP-CS-Fixer:

vendor/bin/rector process && vendor/bin/pint

Otherwise you ship diffs full of \Symfony\Component\... in the middle of method bodies and your reviewers will hate you.

Constructor injection rules are opinionated

SYMFONY_CONSTRUCTOR_INJECTION will move $this->get() / $this->container->get() into constructor properties. That's correct Symfony practice and largely wrong for Laravel's container (app(), facades, resolve()). If a file is Laravel-shaped, exclude it — don't let the rule "fix" a facade call into a constructor dependency that breaks lazy resolution or makes tests harder to write.

Don't chain it with PHP-CS-Fixer in the same pre-commit hook

Both rewrite files. Run Rector → commit-check → CS fixer → commit-check, in that order. In a single lint-staged pass they can fight, and git add captures whichever wrote last.

The extension point: write your own rule

The package's real long-term value for a Laravel dev isn't its rule list — it's that it teaches you the shape of a Rector rule. When you find yourself doing the same mechanical refactor across 30 files (say, replacing a facade with an injected dependency, or migrating $dates to $casts), write a rule in rector-rules/ in your own repo. Rector's AbstractRector + a node-type pair is genuinely 40 lines of code. The Symfony package is the best available reference implementation of a domain-specific rule collection.

Bottom line for a Laravel shop

Adopt Rector immediately. Adopt rector-symfony only if you own code with a Symfony composer.json constraint — a dual-framework package, a bundle, or a component-heavy shared library. For a vanilla Laravel app, installing it costs nothing and does nothing; installing it and then enabling SYMFONY_7x sets because "newer is better" is how you get a 300-file diff that breaks your test suite on a Tuesday afternoon.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
store-doc/laravel-storage-driver
plus-it/laravel-translation-manager
diephp/perhaps
diephp/telegraph
osmaviation/spreadsheet
microsoft/fluentui-system-icons
mmccook/php-json-canonicalization-scheme
middlewares/firewall
qubiqx/montapacking
nyoncode/laravel-package-console
pnscripts/filament-broadquill-lite
pnscripts/laravel-sentfold-lite
vleap/mx-sdk-laravel-internal
twidpay/utils
htetmyathlaing/scout-elasticsearch-driver
artisan-toolbox/front-awesome
ls/client-assistant
users-au/laravel-client
cgv/localized-resource
noith/settings