Efficience IT
·Outils

Rector et ses pouvoirs : maîtrisez l'évolution de votre code Symfony

Par Louis-Arnaud Catoire

Comprendre Rector et l'AST

Rector est un outil de refactoring automatique pour PHP. Là où un développeur modifie du code manuellement — renommer une méthode, ajouter un type hint, remplacer une annotation par un attribut — Rector effectue ces transformations de manière programmatique sur l'ensemble d'une base de code.

Le mécanisme repose sur l'AST (Abstract Syntax Tree). Plutôt que de manipuler du texte brut avec des expressions régulières, Rector utilise nikic/php-parser pour convertir chaque fichier PHP en arbre syntaxique. Chaque élément du code — classe, méthode, affectation, condition — devient un noeud typé dans cet arbre. Une affectation $x = 1; produit un noeud Assign contenant un noeud Variable et un noeud LNumber. Un double !!$var se traduit par deux noeuds BooleanNot imbriqués.

Le cycle de transformation suit trois phases : le parsing convertit le source en AST, la traversée applique les règles sur chaque noeud correspondant, et l'impression reconvertit l'arbre modifié en code PHP en préservant le style d'origine (indentation, espaces) pour minimiser les diffs. Cette approche structurelle garantit que seuls les patterns exacts sont modifiés, ce qui élimine la catégorie entière de bugs liés au rechercher-remplacer textuel.

Installation et premier lancement

Rector s'installe comme dépendance de développement via Composer :

composer require rector/rector --dev

La génération du fichier de configuration se fait en une commande :

php vendor/bin/rector init

Le fichier rector.php produit constitue le point d'entrée de toute la configuration :

return static function (RectorConfig $rectorConfig): void {
    $rectorConfig->paths([
        __DIR__ . '/config',
        __DIR__ . '/public',
        __DIR__ . '/src',
        __DIR__ . '/tests',
    ]);
    $rectorConfig->sets([
        LevelSetList::UP_TO_PHP_83,
    ]);
};

La prévisualisation des changements sans application se fait avec --dry-run, l'application effective sans ce flag :

php vendor/bin/rector process --dry-run
php vendor/bin/rector process

Stratégie de rule sets

Le choix et l'ordonnancement des rule sets déterminent la qualité d'une migration. Rector propose plusieurs catégories de sets, chacune avec un périmètre précis :

  • SetList::DEAD_CODE supprime le code mort : variables inutilisées, conditions toujours vraies, méthodes privées jamais appelées.
  • SetList::CODE_QUALITY simplifie les conditions, élimine les redondances, améliore la lisibilité.
  • SetList::CODING_STYLE uniformise le style : ternaires, early returns, conventions d'écriture.
  • SetList::TYPE_DECLARATION ajoute les type hints manquants sur les paramètres, retours et propriétés.
  • SetList::NAMING renomme variables et méthodes selon les conventions PSR.

Pour Symfony, des sets dédiés couvrent chaque version majeure et les transformations structurelles :

$rectorConfig->sets([
    SymfonySetList::SYMFONY_60,
    SymfonySetList::ANNOTATIONS_TO_ATTRIBUTES,
    SymfonySetList::SYMFONY_CODE_QUALITY,
    SymfonySetList::SYMFONY_CONSTRUCTOR_INJECTION,
]);

La règle ANNOTATIONS_TO_ATTRIBUTES est particulièrement stratégique lors du passage à Symfony 6.x. Elle transforme les annotations en attributs PHP 8 sur les routes, les entités Doctrine (@Entity, @Column, @OneToMany) et les contraintes de validation (@Assert\NotBlank, @Assert\Email) :

#[Route('/users', name: 'user_list', methods: ['GET'])]
public function list(): Response

L'ordre d'application compte. Commencer par DEAD_CODE réduit la surface de code avant d'appliquer les transformations plus lourdes. Enchaîner avec CODE_QUALITY puis les sets de version produit des diffs plus lisibles et des reviews plus efficaces.

Exclure des fichiers

Certains fichiers méritent un traitement spécifique ou une exclusion temporaire :

$rectorConfig->skip([
    SimplifyIfReturnBoolRector::class => [
        __DIR__ . '/src/ComplicatedFile.php',
        __DIR__ . '/src/ComplicatedDirectory',
    ],
]);

Intégration CI et workflow par lots

Rector prend toute sa valeur quand il est intégré au pipeline d'intégration continue. Le principe est simple : --dry-run dans la CI vérifie qu'aucun fichier ne nécessite de transformation, garantissant que tout code poussé est déjà conforme.

Dans un workflow GitHub Actions :

- name: Rector
  run: vendor/bin/rector process --dry-run

Si Rector détecte des modifications nécessaires, le pipeline échoue. Les développeurs exécutent Rector localement, commitent les changements et poussent à nouveau. Ce mécanisme fonctionne identiquement avec GitLab CI, Jenkins ou tout autre outil.

Le workflow par lots est la clé d'une migration maîtrisée. Plutôt que d'activer tous les sets d'un coup et de produire un diff de plusieurs milliers de lignes, chaque itération cible un set ou une règle unique :

  1. Activer une règle dans rector.php.
  2. Exécuter rector process, vérifier le diff.
  3. Lancer la suite de tests complète.
  4. Commiter et créer une pull request dédiée.
  5. Passer à la règle suivante.

Ce découpage produit des PRs atomiques, facilite la review et permet de reverter chirurgicalement en cas de régression.

Règles personnalisées

Les règles intégrées couvrent les cas génériques. Les besoins spécifiques à un projet — remplacer un helper legacy maison, migrer un pattern interne, appliquer une convention d'équipe — nécessitent des règles sur mesure.

Une règle personnalisée étend AbstractRector et implémente trois méthodes :

use PhpParser\Node;
use PhpParser\Node\Expr\FuncCall;
use Rector\Rector\AbstractRector;
use Symplify\RuleDocGenerator\ValueObject\RuleDefinition;

final class ReplaceLegacyHelperRector extends AbstractRector
{
    public function getRuleDefinition(): RuleDefinition
    {
        return new RuleDefinition('Replace legacy_helper() calls with new_helper()', []);
    }

    public function getNodeTypes(): array
    {
        return [FuncCall::class];
    }

    public function refactor(Node $node): ?Node
    {
        if (! $this->isName($node, 'legacy_helper')) {
            return null;
        }

        $node->name = new Node\Name('new_helper');

        return $node;
    }
}

getNodeTypes() déclare les types de noeuds AST ciblés. refactor() reçoit chaque noeud correspondant et retourne le noeud modifié ou null si aucune transformation ne s'applique. La puissance de cette API réside dans l'accès complet au graphe AST : on peut inspecter les noeuds parents, les annotations de type via PHPStan, la portée des variables, et composer des transformations complexes impliquant plusieurs noeuds.

Pour une équipe qui maintient des dizaines de règles internes, structurer ces règles dans un package dédié avec leurs propres tests PHPUnit garantit leur fiabilité lors des mises à jour de Rector.

Manipulation avancée de l'AST

Derrière l'API simplifiée d'AbstractRector se cache la mécanique complète de nikic/php-parser. Les Node Visitors permettent d'intervenir à différents moments de la traversée (beforeTraverse, enterNode, leaveNode, afterTraverse). Rector orchestre ces visitors dans un ordre déterministe, résout les dépendances entre règles et gère les cas où une transformation invalide le contexte d'une autre.

Comprendre ce niveau de détail devient nécessaire quand une règle doit effectuer des transformations multi-noeuds : déplacer une méthode d'une classe vers un trait, extraire un service à partir d'un code procédural, ou restructurer une hiérarchie d'héritage. Ces opérations requièrent de manipuler des noeuds ClassLike, ClassMethod et Stmt en coordination, en s'assurant que les imports (use statements) et les références croisées restent cohérents.

Rector comme outil de gouvernance technique

À l'échelle d'une organisation qui maintient plusieurs applications Symfony, Rector dépasse le rôle d'outil de migration ponctuel pour devenir un instrument de gouvernance du code.

Un set de règles partagé entre équipes, distribué comme package Composer interne, encode les standards techniques de l'organisation. Quand une décision architecturale est prise — migrer de ArrayCollection vers un wrapper typé, bannir les appels statiques dans la couche domaine, imposer les readonly classes — elle se traduit en règle Rector. La CI l'applique automatiquement sur tous les projets. Le standard n'est plus un document qu'on espère voir respecté, mais une contrainte enforcée par l'outillage.

Migration par phases sur les grandes bases de code

Sur un monolithe de plusieurs centaines de milliers de lignes, une migration frontale est irréaliste. L'approche par phases utilise la capacité de Rector à cibler des répertoires spécifiques :

php vendor/bin/rector process src/Module/Billing

Chaque module migre indépendamment, avec sa propre PR, ses propres tests et sa propre validation. Les modules critiques passent en dernier, après que l'équipe a accumulé de la confiance sur les modules périphériques. Un fichier rector.php par module peut coexister temporairement pour gérer les vitesses de migration différentes.

Gestion du risque

Rector ne peut pas garantir l'équivalence comportementale de toutes les transformations. Les transformations syntaxiques pures (promotion de propriétés dans le constructeur, match expression) sont sûres. Les transformations sémantiques (remplacement de patterns, modification de nommage) exigent une couverture de tests solide et une review humaine.

La stratégie de mitigation repose sur trois piliers : une suite de tests avec un coverage suffisant sur le code transformé, un passage PHPStan au niveau le plus élevé possible avant et après migration, et des PRs suffisamment petites pour qu'un reviewer puisse les analyser en profondeur. Si un de ces piliers manque, ralentir le rythme de migration est la décision responsable.

Conclusion

Rector transforme la maintenance et l'évolution d'une base de code PHP d'un effort manuel en processus systématique. Pour un développeur confirmé, c'est un accélérateur de migrations. Pour un lead, c'est un outil d'intégration continue qui enforce les standards. Pour un architecte, c'est un levier de gouvernance technique qui permet d'appliquer des décisions structurelles à l'échelle d'une organisation. La condition de son efficacité reste constante : une couverture de tests fiable, une progression par lots maîtrisés et une review humaine sur les transformations à risque.

Pour aller plus loin