Skip to content

[composer-based] Bond version-specific rules to composer package constraints - #979

Merged
TomasVotruba merged 2 commits into
mainfrom
composer-based-version-bound-rules
Aug 1, 2026
Merged

[composer-based] Bond version-specific rules to composer package constraints#979
TomasVotruba merged 2 commits into
mainfrom
composer-based-version-bound-rules

Conversation

@TomasVotruba

@TomasVotruba TomasVotruba commented Aug 1, 2026

Copy link
Copy Markdown
Member

Same shape as rector-phpunit #754 / #747, applied to Symfony.

What

A single new set, config/sets/symfony/composer-based.php, holding rules bound to the exact Symfony package version their target API was added in. Each rule declares its own constraint:

final class SecurityAttributeToIsGrantedAttributeRector extends AbstractRector implements ComposerPackageConstraintInterface
{
    public function provideComposerPackageConstraint(): ComposerPackageConstraint
    {
        return new ComposerPackageConstraint('symfony/security-http', '>=6.2');
    }
}

So the rule runs on any project with that package installed, no matter which Symfony version the upgrade started from - instead of only when the symfony62 set is picked.

Effect

A project on symfony/security-http 6.4, upgrading from Symfony 5.4, gets the #[IsGranted] conversion without running the 6.2 set:

-#[Security("is_granted('ROLE_ADMIN')")]
+#[IsGranted('ROLE_ADMIN')]
 public function index()
 {
 }

And the same rule is skipped on a project still on symfony/security-http 5.4, where the attribute does not exist yet.

Trigger

Symfony has no single package like phpunit/phpunit to trigger on, so every package used inside the set registers it, from the lowest version its rules require:

private const array COMPOSER_BASED_TRIGGER_PACKAGES = [
    'symfony/config' => '>=4.2',
    'symfony/process' => '>=4.2',
    'symfony/event-dispatcher' => '>=4.3',
    'symfony/console' => '>=4.4',
    // ...
];

Notes

  • Existing sets stay untouched - the rules are registered in both places, and the constraint makes the duplicate harmless.
  • composer.json is untouched as well. A rule can only be bonded once its package is a dev dependency, otherwise the constraint filters it out and its test silently stops transforming. Rules for symfony/messenger, symfony/twig-bridge, symfony/doctrine-bridge and symfony/framework-bundle are therefore left for a follow-up.
  • Same for the 7.3 rules of symfony/console, symfony/twig-bundle and symfony/security-core: rector/rector-src requires symfony/console ^6.4.24 and the root requires symfony/config ^8.1, so those versions cannot be installed here at all.

…traints

Add config/sets/symfony/composer-based.php, a single set holding rules bound
to the exact Symfony package version their target API was added in, mirroring
the rector-phpunit composer-based set.

Each rule declares its own constraint via ComposerPackageConstraintInterface,
so it is registered once here instead of being repeated in every Symfony
version set to cover a direct upgrade from an older version.

Symfony has no single package to trigger on, so every package used inside the
set registers it as a ComposerTriggeredSet, from the lowest version its rules
require.

Existing sets stay untouched.
Bonding a rule to a package only works when that package is installed,
otherwise its test silently stops transforming. Keep composer.json as is and
bond only rules whose package is already a dev dependency.

Removes the symfony/messenger, symfony/twig-bridge and symfony/doctrine-bridge
rules from the composer-based set.
@TomasVotruba

Copy link
Copy Markdown
Member Author

First wave, let's ship to allow testing in core.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

1 participant