Aller au contenu principal
Retour au blog

Config Rector 2.7 pour Symfony : monter de version sans big bang

Rector 2.7 sur Symfony : withComposerBased, withPhpSets à la place de SetList::PHP_*, montée par niveaux de types, et un passage vers Symfony 8 testé.

Flavien Métivier · 8 min de lecture
Sommaire · 7 parties

Votre rector.php date probablement de 2023 : une liste de SetList copiée d’un projet à l’autre, des constantes PHP figées, et une migration Symfony qu’on repousse parce qu’elle produirait quatre cents fichiers modifiés d’un coup. Rector 2.7 (6 octobre 2026) règle une bonne partie du problème : les règles s’activent désormais selon les versions réellement installées dans votre projet. Voici la configuration d’un projet Symfony en 2026, ce qui a changé, et une méthode pour avancer par petits diffs relisibles — tout a été exécuté sur un squelette Symfony 7.4, puis 8.1.

Si vous découvrez l’outil, commencez par les bases de Rector : installation, règles, premier passage. Cet article suppose un rector.php existant.

Ce qui a changé entre Rector 2.6 et 2.7

  • 2.6.0 (3 août) introduit les sets basés sur Composer.
  • 2.6.5 (30 août) déprécie les constantes SetList::PHP_* et rend l’invalidation du cache plus fine.
  • 2.6.6 et 2.6.7 (septembre) ajoutent --filter, --max-changes, --only répétable et la commande validate-config.
  • 2.7.0 (6 octobre) apporte de nouvelles règles, dont ConstructorReadonlyAssignToDefaultRector, et passe sur PHPStan 2.3.

Trois changements, un seul effet : la configuration devient déclarative et suit votre projet. La cible tient en une dizaine de lignes :

php
<?php
// rector.php : Rector 2.7.0, Symfony 7.4, PHP 8.4+
declare(strict_types=1);

use Rector\Config\RectorConfig;

return RectorConfig::configure()
    ->withPaths([__DIR__ . '/src', __DIR__ . '/tests'])
    // règles liées aux versions réellement installées
    ->withComposerBased(doctrine: true, phpunit: true, symfony: true, twig: true)
    // annotations @Route -> attributs #[Route]
    ->withAttributesSets(symfony: true)
    // sans argument : lit la contrainte "php" de composer.json
    ->withPhpSets()
    ->withPreparedSets(deadCode: true, codeQuality: true)
    // montée progressive : relevée PR après PR
    ->withTypeCoverageLevel(20)
    ->withImportNames(removeUnusedImports: true);

withComposerBased : laisser les versions installées choisir les règles

withComposerBased() active dans Rector les règles qui correspondent aux versions de Symfony, Doctrine, PHPUnit et Twig réellement installées. Avant, monter de Symfony 6.4 à 7.x consistait à empiler des sets versionnés à la main, avec le risque d’appliquer une règle de migration à un projet qui n’était pas encore sur la bonne version. Désormais, chaque règle porte une contrainte — par exemple symfony/console >=7.4 — que Rector compare à la version installée. Pas de montée de version, pas de règle qui anticipe.

La commande rector composer-based explique quelles règles sont actives, et pourquoi. Sortie réelle sur notre squelette Symfony 7.4.21 avec Doctrine ORM 3.7.5, PHPUnit 11.5.57 et Twig 3.30 :

bash
$ vendor/bin/rector composer-based
 Rule                                 Package          Requires  Installed  Active
 CastDoctrineExprToStringRector       doctrine/orm     >=3.0     3.7.5.0    yes
 AnyMatcherToNewAnyInvokedCountRector phpunit/phpunit  >=12.5    11.5.57.0  no
 ChangeCollectionTypeOption...Rector  symfony/form     >=2.7     -          no
 ...
 ! [NOTE] 213 of 370 composer package bound items are active

Un « no » a deux causes possibles : le paquet est absent (-), ou la contrainte dépasse la version installée — les règles PHPUnit 12.5 s’activeront d’elles-mêmes le jour où vous monterez. C’est la propriété recherchée : le même rector.php sert avant et après la montée de version. Pour une PR qui ne contient que ces règles, rector process --composer-based les isole. Les détails propres à PHPUnit sont dans notre article sur la migration vers PHPUnit 13.

Une limite : les notes de la 2.6.0 indiquent que PHPUnit et Symfony ont été migrés en premier et que la couverture complète est en cours. Et withComposerBased(symfony: true) seul n’a pas converti nos annotations @Route : il a fallu withAttributesSets(symfony: true), qui a produit #[Route(path: '/invoices', name: 'invoice_index', methods: ['GET'])]. Côté Doctrine, doctrine: true a bien converti @ORM\Entity et @ORM\Column en attributs.

withPhpSets() remplace SetList::PHP_*

Depuis la 2.6.5, les constantes SetList::PHP_82, PHP_83… sont dépréciées. Elles fonctionnent encore en 2.7.0, mais chaque exécution affiche un avertissement : « The per-version PHP set is deprecated. Use "withPhpSets()" or "withPhpLevel()" instead ». Les méthodes withPhp53Sets() à withPhp74Sets() sont dépréciées aussi.

withPhpSets() accepte un flag par version, de php53 à php86 : php85 et php86 existent bien en 2.7.0. Trois règles, lues dans le code de Rector : un seul appel par configuration, un seul flag à la fois — c’est un plafond, les versions inférieures sont incluses —, et de préférence aucun argument, auquel cas Rector lit la contrainte php de composer.json.

Le piège que nous avons rencontré : avec "php": ">=8.2" dans composer.json, withPhpSets(php86: true) n’a pas réécrit min(10, max(0, $v)). Avec "php": ">=8.6", la règle MinMaxToClampRector du set 8.6 a produit clamp($v, 0, 10). Le flag explicite ne dépasse pas la plateforme déclarée : montez d’abord require.php. Pour choisir cette cible avant la fin de support de PHP 8.2, voyez notre checklist PHP 8.6 et fin de support de PHP 8.2. Rector réécrit la syntaxe ; il ne détecte pas les changements de comportement à l’exécution, comme les nouveaux défauts de session.

À quoi ressemble le résultat sur un service « ancien », avec withPhpSets() et les prepared sets deadCode et codeQuality — avant :

php
class InvoiceMailer
{
    private $logger;
    private $sender;

    public function __construct(LoggerInterface $logger, string $sender)
    {
        $this->logger = $logger;
        $this->sender = $sender;
    }

    public function send(array $invoices, $dryRun = false)
    {
        $unused = 42;
        foreach ($invoices as $invoice) {
            if ($dryRun == true) {
                continue;
            } else {
                $this->logger->info(sprintf('Send %s', $invoice->getNumber()));
            }
        }
        return count($invoices) > 0 ? true : false;
    }
}

Migration Symfony en vue ?

Bear Upgrade migre votre codebase 2,5× à 4,5× moins cher qu'une ESN, selon sa maturité.

Estimer ma migration →

Après : promotion de propriétés en readonly, variable inutile supprimée, else retiré, ternaire simplifié. La comparaison $dryRun == true, elle, n’a pas été touchée : la revue de diff et la suite de tests restent les juges.

php
class InvoiceMailer
{
    public function __construct(private readonly LoggerInterface $logger, private readonly string $sender)
    {
    }

    public function send(array $invoices, $dryRun = false)
    {
        foreach ($invoices as $invoice) {
            if ($dryRun == true) {
                continue;
            }
            $this->logger->info(sprintf('Send %s', $invoice->getNumber()));
        }
        return count($invoices) > 0;
    }
}

Monter la couverture de types par niveaux

Sur une base existante, activer withPreparedSets(typeDeclarations: true) d’un coup est précisément le big bang à éviter. Rector propose des niveaux : withTypeCoverageLevel(), withDeadCodeLevel(), withPhpLevel(). Chaque incrément ajoute la règle suivante, de la plus sûre à la plus intrusive. Sur notre projet : niveau 10, aucun changement ; niveau 20, des types de retour sur les actions de contrôleur et les ternaires stricts ; niveau 40, les propriétés typées depuis le constructeur. Un niveau peut donc ne rien changer chez vous. Les deux approches sont exclusives — les combiner est refusé : « Your config already enables type declarations set ».

Activez withImportNames() dès le premier jour : sans lui, les règles injectent des noms de classe complets en ligne (\Symfony\Component\HttpFoundation\Response), et vous devrez faire une PR cosmétique pour les nettoyer. Côté verrouillage, Rector écrit les types et PHPStan empêche qu’ils régressent : relevez le niveau de couverture PR après PR, avec une suite verte à chaque palier.

Monter de Symfony 7.4 à 8.x sans big bang

Deux faits d’abord. Symfony 8 exige PHP 8.4.1 au minimum : la montée PHP passe en premier. Et à la mi-octobre 2026, Packagist propose Symfony 8.0 et 8.1 en version stable. La méthode, où chaque étape est une PR courte :

  • Rester sur 7.4 LTS, en PHP 8.4 ou plus, et corriger les dépréciations avec la configuration ci-dessus. Observé : $app->add($cmd) devient $app->addCommand($cmd), et Routing\Annotation\Route devient Routing\Attribute\Route.
  • Convertir les annotations en attributs avec doctrine: true et withAttributesSets(symfony: true).
  • Typer par paliers : des signatures plus strictes facilitent le passage.
  • Lancer rector composer-based avant et après le bump pour voir quelles règles s’activent.
  • Monter : composer update "symfony/*" --with-all-dependencies, avec extra.symfony.require à 8.1.*.
  • Relancer Rector, les tests et PHPStan — avec le même rector.php qu’à la première étape.

Ce que nous avons mesuré à l’étape 4 tempère les attentes : passer de Symfony 7.4.21 à 8.1.9 a fait passer le compteur de 213 à 215 règles actives sur 370. Le gain automatique du saut de version est donc modeste ; l’essentiel du travail se fait avant, en éliminant les dépréciations de 7.4, et le reste à la main avec les guides UPGRADE. Pour ce tri, suivez notre article sur l’élimination des dépréciations de Symfony 7.4 avant la 8.0. Rector n’est pas une migration Symfony 8 automatique : c’est ce qui rend la partie mécanique gratuite.

Rector en CI : verrouiller les gains

Un job qui échoue dès que Rector réécrirait du code empêche la dette de revenir. validate-config vérifie l’hygiène de la configuration sans traiter de fichier. Attention : la documentation « Cache in CI » montre encore --output-format=github, mais Rector 2.7.0 affiche « The "github" output format is deprecated » — préférez json.

yaml
# .github/workflows/rector.yml : échoue si Rector modifierait du code
name: Rector
on:
  pull_request:
permissions:
  contents: read
jobs:
  rector:
    runs-on: ubuntu-24.04
    steps:
      - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
        with:
          persist-credentials: false
      - uses: shivammathur/setup-php@34a5396826718e0013f08e3e639d1c315d5f6b23 # 2.35.0
        with:
          php-version: '8.5'
          coverage: none
      - run: composer install --no-interaction --prefer-dist
      - run: vendor/bin/rector validate-config
      - run: vendor/bin/rector process --dry-run --no-progress-bar --output-format=json

Pour découper un gros chantier, deux options de la 2.6.7 sont précieuses : --filter="/Controller/" limite l’exécution à un sous-ensemble de chemins, et --max-changes=N s’arrête après N modifications. Une PR par dossier, une PR par palier.

Les pièges, en une liste

  • withPhpSets(php86: true) ne fait rien tant que composer.json annonce >=8.2 : mettez require.php à jour d’abord.
  • Un seul appel à withPhpSets(), et jamais withTypeCoverageLevel() avec typeDeclarations: true.
  • Plus de SetList::PHP_*, de SetList::IF ni de --output-format=github : dépréciés.
  • Le résultat de composer-based dépend de ce qui est installé : lancez Rector sur la même installation que la CI, idéalement dans le même conteneur.
  • Rector réécrit, il n’analyse pas : gardez PHPStan en CI pour la détection.

Trois actions pour cette semaine : remplacer vos SetList::PHP_* par withPhpSets() sans argument, ajouter withComposerBased() et lire la sortie de rector composer-based, et planifier la prochaine étape de montée de version en une PR par palier.

Rector automatise la partie mécanique, mais c’est le découpage en paliers et la suite de tests qui rendent une montée de version livrable sans geler la roadmap. Si votre équipe n’a pas le temps de tenir ce chantier en parallèle du produit, c’est exactement le périmètre de Bear Upgrade pour Symfony : Rector, tests et Merge Requests prêtes à merger, palier par palier.

Cet article vous a été utile ? Partagez-le.

Portrait de Flavien Métivier, fondateur de The Bearded Bear : barbe longue, lunettes, sur fond sombre

Écrit par

Flavien Métivier

Expert PHP/Symfony indépendant depuis 2014, ancien CTO de Kartable et d'Extrabat. Plus de 80 projets menés, de l'audit à la migration.

Mon parcours

À lire ensuite

Migration Symfony en vue ?

Bear Upgrade migre votre codebase 2,5× à 4,5× moins cher qu'une ESN, selon sa maturité.

Newsletter Bear Bytes

Veille Claude Code, Symfony et PHP, retours d'expérience terrain et conseils techniques, directement dans votre boîte mail.