Rector : le sergent-instructeur de votre code qui n'a pas le temps pour vos conneries

Temps de lecture : 3 min

Découvrez Rector, l'outil qui transforme votre code PHP sans patience pour vos erreurs de débutant.

Il y a des gens qui aiment bien passer leur temps à corriger manuellement le même putain de problème de code dans 743 fichiers différents. Ils adorent ça. Ils se lèvent le matin, ils prennent leur café, et ils se disent : "Tiens, et si aujourd'hui je passais 9 heures à chercher toutes les occurrences de new Class() pour les remplacer par new class()?" Ces gens-là sont soit masochistes, soit ils n'ont pas encore découvert Rector.

C'est quoi au juste Rector ?

Rector, c'est un outil de refactoring automatique pour PHP. C'est comme si tu avais un développeur senior qui serait à la fois ton coach personnel et ton psychiatre de code. Il regarde ton code, il secoue la tête d'un air désapprobateur, puis il te dit : "T'inquiète, je m'en occupe" — et pouf, ton code est propre.

Plus sérieusement, Rector est un outil qui permet d'appliquer automatiquement des transformations sur ton code PHP. Il peut :

Et le plus beau dans tout ça ? Il fait tout ça sans se plaindre, sans te juger, et sans te demander une augmentation.

Pourquoi tu devrais l'utiliser

Moi je connais deux types de développeurs :

  1. Ceux qui utilisent Rector
  2. Ceux qui aiment passer leur week-end à faire des chercher/remplacer

Si tes dans la deuxième catégorie, tu as un problème.

Les avantages ?

Ma configuration Rector

Allez, je vais te montrer ma config Rector. C'est un peu comme montrer sa collection de timbres, mais pour les nerds :

<?php

declare(strict_types=1);

use App\Rector\RemovePhpAttributeImportsRector;
use Rector\Config\RectorConfig;
use Rector\Doctrine\Set\DoctrineSetList;
use Rector\Php80\Rector\Class_\ClassPropertyAssignToConstructorPromotionRector;
use Rector\Set\ValueObject\LevelSetList;
use Rector\Set\ValueObject\SetList;
use Rector\Symfony\Set\SymfonySetList;
use Rector\TypeDeclaration\Rector\Property\TypedPropertyFromStrictConstructorRector;
use Rector\ValueObject\PhpVersion;

try {
    return RectorConfig::configure()
        ->withParallel(120, 16, 10)
        ->withPhpVersion(PhpVersion::PHP_84)
        ->withPhpSets(php84: true)
        ->withPaths([
            __DIR__.'/src',
            __DIR__.'/tests',
        ])
        ->withAttributesSets(
            symfony: true,
            doctrine: true,
            gedmo: true,
        )
        ->withSets([
            SetList::EARLY_RETURN,
            SetList::STRICT_BOOLEANS,
            SetList::CODE_QUALITY,
            SetList::CODING_STYLE,
            SetList::DEAD_CODE,
            SetList::PRIVATIZATION,
            SetList::TYPE_DECLARATION,
            LevelSetList::UP_TO_PHP_84,
            LevelSetList::UP_TO_PHP_83,
            LevelSetList::UP_TO_PHP_82,
            LevelSetList::UP_TO_PHP_81,
            LevelSetList::UP_TO_PHP_80,
            DoctrineSetList::ANNOTATIONS_TO_ATTRIBUTES,
            DoctrineSetList::DOCTRINE_CODE_QUALITY,
            SymfonySetList::SYMFONY_72,
            SymfonySetList::SYMFONY_CODE_QUALITY,
            SymfonySetList::SYMFONY_CONSTRUCTOR_INJECTION,
        ])
        ->withRules([
            TypedPropertyFromStrictConstructorRector::class,
            RemovePhpAttributeImportsRector::class,
        ])
        ->withSkip([
            ClassPropertyAssignToConstructorPromotionRector::class => __DIR__.'/src/Entity',
        ]);
} catch (Rector\Exception\Configuration\InvalidConfigurationException $e) {
    // Gestion de l'erreur ou rapport silencieux
}

Alors, prenons cette configuration ligne par ligne :

Configuration de base

return RectorConfig::configure()

Ça, c'est pour dire "Hé Rector, écoute-moi bien, j'ai des instructions pour toi".

Les paramètres de performance

->withParallel(120, 16, 10)

C'est comme quand tu dis à tes 10 stagiaires : "Allez, vous avez tous la même tâche, celui qui la finit le premier a le droit de rentrer chez lui". Sauf que là, c'est plus comme 120 stagiaires, 16 par équipe, avec un timeout de 10 secondes. Ça va VITE, mon gars.

La version de PHP ciblée

->withPhpVersion(PhpVersion::PHP_84)
->withPhpSets(php84: true)

C'est pour dire à Rector : "Mec, mon code doit être prêt pour PHP 8.4, parce que je suis un putain de visionnaire et j'ai besoin de ce garbage collector optimisé pour gagner 0.002 millisecondes sur mon temps de chargement".

Les chemins à analyser

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

"Regarde Rector, je veux que tu t'occupes de mon code source et de mes tests, mais t'as pas besoin d'aller fourrer ton nez dans mes dépendances, ça va bien se passer".

Les ensembles d'attributs modernes

->withAttributesSets(
    symfony: true,
    doctrine: true,
    gedmo: true,
)

Là, on dit à Rector de transformer les vieilles annotations (tu sais, ces lignes qui commencent par @ et qui ressemblent à des tweets de boomer) en attributs modernes de PHP 8 (ces trucs avec des crochets qui ressemblent à des décorations de Noël).

Les ensembles de règles

->withSets([
    SetList::EARLY_RETURN,  // Sortir tôt des fonctions, comme quand tu quittes une soirée nulle
    SetList::STRICT_BOOLEANS,  // Parce que peut-être c'est pas une réponse en informatique
    SetList::CODE_QUALITY,  // Pour que ton code soit plus beau que ta photo Tinder
    SetList::CODING_STYLE,  // Pour unifier ton style, genre pas un espace ici et trois là
    SetList::DEAD_CODE,  // Pour virer le code zombie qui sert à rien
    SetList::PRIVATIZATION, // Pour cacher tes propriétés comme tes sentiments
    SetList::TYPE_DECLARATION, // Pour mettre des types partout, parce que l'anarchie c'est fini
    
    // Pour monter de version en version comme un upgrade de Pokémon
    LevelSetList::UP_TO_PHP_84,
    LevelSetList::UP_TO_PHP_83,
    LevelSetList::UP_TO_PHP_82,
    LevelSetList::UP_TO_PHP_81,
    LevelSetList::UP_TO_PHP_80,
    
    // Pour Doctrine, parce que les annotations c'est tellement 2019
    DoctrineSetList::ANNOTATIONS_TO_ATTRIBUTES,
    DoctrineSetList::DOCTRINE_CODE_QUALITY,
    
    // Pour Symfony, parce que si tu utilises pas la dernière version, tu es ringard
    SymfonySetList::SYMFONY_72,
    SymfonySetList::SYMFONY_CODE_QUALITY,
    SymfonySetList::SYMFONY_CONSTRUCTOR_INJECTION,
])

C'est comme un menu dégustation dans un resto étoilé, sauf que c'est pour ton code. Tu prends un peu de tout, et à la fin, ton code ressemble à quelque chose qu'un développeur senior aurait écrit, même si tu as commencé à coder il y a 3 semaines.

Les règles personnalisées

->withRules([
    TypedPropertyFromStrictConstructorRector::class,
    RemovePhpAttributeImportsRector::class,
])

Là, j'ajoute deux règles spécifiques :

Les exceptions

->withSkip([
    ClassPropertyAssignToConstructorPromotionRector::class => __DIR__.'/src/Entity',
])

"Rector, tu es cool, mais s'il te plaît, ne transforme pas les propriétés de mes entités en paramètres de constructeur promoted, j'ai mes raisons". C'est comme dire à ton coiffeur "Tu coupes tout, sauf la mèche de devant".

Et la règle custom, on en parle ?

J'ai créé une règle custom dans mon projet. Parce que parfois, même les outils parfaits ont besoin d'être perfectionnés. C'est comme mettre un sticker sur ta Ferrari.

Ma règle RemovePhpAttributeImportsRector, c'est pour virer les imports d'attributs PHP natifs qu'on n'a pas besoin d'importer. Par exemple, au lieu de :

use Override;

class Truc {
    #[Override]
    public function method() {}
}

On a directement (remarque le backslash qui indique le namespace global) :

class Truc {
    #[\Override]
    public function method() {}
}

Pourquoi ? Parce que c'est plus propre, plus explicite, et ça évite de surcharger ton espace de noms avec des trucs que PHP connaît déjà. C'est comme si tu te présentais à chaque fois en disant "Bonjour, je suis Humain Pierre" au lieu de juste "Bonjour, je suis Pierre".

La règle est plutôt simple :

class RemovePhpAttributeImportsRector extends AbstractRector
{
    private const array PHP_ATTRIBUTES = [
        'Override',
        'Attribute',
        'AttributeOverride',
        'Deprecated',
        'Pure',
        'ReturnTypeWillChange',
        'Template',
        'Transient',
        'TransientDeprecated',
    ];

    // ... le reste du code ...

    private function isPhpAttribute(UseUse $use): bool
    {
        $lastName = $use->name->getLast();
        return in_array($lastName, self::PHP_ATTRIBUTES, true);
    }
}

Comment utiliser Rector dans tes projets

1. Installation

D'abord, installes-le dans ton projet :

composer require --dev rector/rector

2. Configuration

Crée un fichier rector.php à la racine de ton projet. Tu peux commencer par un truc simple comme :

<?php

declare(strict_types=1);

use Rector\Config\RectorConfig;
use Rector\Set\ValueObject\SetList;

return RectorConfig::configure()
    ->withPhpVersion(PhpVersion::PHP_82)
    ->withPaths([
        __DIR__ . '/src',
    ])
    ->withSets([
        SetList::CODE_QUALITY,
        SetList::DEAD_CODE,
    ]);

3. Exécution

Pour voir ce que Rector va changer sans toucher à ton code :

vendor/bin/rector process --dry-run

Et quand tu es prêt à faire les changements pour de vrai :

vendor/bin/rector process

Dans mon projet, j'ai intégré Rector à mon Makefile pour pouvoir l'exécuter facilement :

qa-fix-php: ## Corriger le code PHP avec php-cs-fixer et rector dans Docker
    @docker run -v $(PWD):/app -w /app -t --rm -e PHP_CS_FIXER_IGNORE_ENV=1 php:8.4-cli-alpine php -d memory_limit=-1 ./vendor/bin/php-cs-fixer fix src tests --config=.php-cs-fixer.dist.php --using-cache=no --verbose --diff
    @docker run -v $(PWD):/app -w /app -t --rm php:8.4-cli-alpine php -d memory_limit=-1 ./vendor/bin/rector process --config=rector.php --clear-cache

Les ensembles de règles ("sets") les plus utiles

Rector a une tonne de sets prédéfinis. C'est comme si t'allais au McDrive et qu'au lieu de commander chaque truc individuellement, tu pouvais juste dire "Donne-moi le menu Maximum Refactoring avec la sauce clean code".

Quelques-uns des plus utiles :

Pour Symfony et Doctrine, il y a des sets spécifiques comme :

Prévenir les erreurs

Si tu as peur que Rector ne casse ton code (ce qui n'arrivera jamais, jamais, jamais... bon, peut-être parfois), voici quelques conseils :

  1. Toujours commiter avant : Si tu es pas le genre de personne qui commit toutes les 5 minutes, fais-le au moins avant de lancer Rector.

  2. Utilise --dry-run d'abord : Ça te montre ce qui va changer sans rien changer.

  3. Lance tes tests après : Si tu as des tests, ce qui est le cas si tu es un développeur responsable (et si tu en n'as pas, on va faire semblant que je t'ai pas posé la question).

  4. Exclue les parties sensibles : Utilise withSkip() pour les morceaux de code où tu préfères que Rector ne touche pas.

Les erreurs fréquentes (comme si tu n'allais pas en faire)

  1. "J'ai lancé Rector sur vendor/" : Bravo, t'as essayé de refactoriser le code des autres. Maintenant, ton projet est cassé et tu ne sais pas pourquoi. Spécifie toujours les bons chemins.

  2. "J'ai utilisé toutes les règles d'un coup" : Ah, l'optimisme. C'est comme vouloir perdre 20 kilos en une semaine. Commence doucement, règle par règle.

  3. "J'ai mis à jour la version PHP sans vérifier la compatibilité" : Et maintenant ton site est down. Surprenant.

  4. "J'ai pas lu la doc" : C'est comme monter un meuble IKEA sans les instructions, et après se plaindre qu'il reste des pièces.

Le mot de la fin

Rector, c'est comme avoir un senior developer qui bosse pour toi 24/7, sans jamais se plaindre, sans demander d'augmentation, et qui ne te juge pas quand tu écris du code de merde à 3h du matin.

Si tu l'utilises pas encore, c'est soit que u 'aimes souffrir, soit que tu es masochiste, soit les deux. Dans tous les cas, tu as besoin d'aide, et cette aide s'appelle Rector.

Ton code te remerciera, ton équipe te remerciera, et ton psy aussi parce que tu auras moins de crises d'angoisse à cause de ton code legacy.

Confidentialité

Ce site utilise Umami pour analyser le trafic de manière anonyme. Acceptez-vous la collecte de données anonymes ?