Een aangepaste Rector-downgraderegel schrijven
In het kort
Sectie met titel “In het kort”De NextPDF-backportpijplijn downgradet de PHP 8.4-broncode van nextpdf/nextpdf, zodat die op PHP 8.1 kan draaien en, voor de kern, op PHP 7.4. Voor de meeste taalfuncties gebruikt de pijplijn de ingebouwde downgradesets van Rector, aangevuld met een kleine set aangepaste regels voor functies die Rector niet dekt.
Deze handleiding laat zien hoe u een nieuwe aangepaste regel toevoegt. De regel volgt dezelfde structuur als de drie regels die al in de repository aanwezig zijn: DowngradeAsymmetricVisibilityRector, DowngradeCloneWithRector en DowngradeTraitConstantsRector. Alle drie staan in rector/rules/, zijn geregistreerd in rector/config/ en hebben fixturegebaseerde tests in tests/Rector/.
Gebruik deze handleiding wanneer een NextPDF-functie PHP-syntaxis gebruikt die het buildtarget niet ondersteunt en Rector er geen ingebouwde downgrade voor heeft. Controleer voordat u begint of Rector echt geen regel heeft. De ingebouwde withDowngradeSets()-keten verwerkt al readonly-klassen, getypeerde klasseconstanten, de pipe-operator en veel andere functies.
Installeren
Sectie met titel “Installeren”U schrijft en draait aangepaste regels binnen de nextpdf-backport-repository. De bijbehorende composer.json declareert de ontwikkeltools.
- Kloon de backport-repository en installeer de afhankelijkheden ervan.
- Bevestig dat Rector en PHPUnit worden gevonden.
git clone https://github.com/nextpdf-labs/backport.gitcd backportcomposer installvendor/bin/rector --versionvendor/bin/phpunit --versionVoor het draaien van de tooling is PHP 8.4 vereist, omdat de regels 8.4-syntaxisbomen manipuleren, ook al richt de buildoutput zich op PHP 8.1 of 7.4. De composer.jsonrequire zet php vast op >=8.4 <9.0. Aangepaste regels worden automatisch geladen in de namespace NextPDF\Backport\, die verwijst naar rector/rules/; tests worden automatisch geladen in NextPDF\Backport\Tests\, die verwijst naar tests/.
Anatomie van een regel
Sectie met titel “Anatomie van een regel”Elke aangepaste regel breidt Rector\Rector\AbstractRector uit en implementeert drie methoden. Het contract is voor alle drie de bestaande regels gelijk.
| Lid | Type | Doel |
|---|---|---|
getRuleDefinition() | RuleDefinition | Een leesbare beschrijving en before/after CodeSample-paren voor de generator van de regeldocumentatie. |
getNodeTypes() | array<class-string<Node>> | De Abstract Syntax Tree (AST)-knoopklassen die de regel bezoekt. Rector roept refactor() alleen voor die klassen aan. |
refactor(Node $node) | ?Node of `Stmt[] | null` |
De declaratie van het knooptype stuurt de regel aan. DowngradeAsymmetricVisibilityRector richt zich op [Property::class, Param::class] omdat asymmetrische zichtbaarheid (public private(set)) zowel op een klasse-eigenschap als op een gepromoveerde constructorparameter kan voorkomen. DowngradeTraitConstantsRector richt zich op [Trait_::class] omdat deze regel het hele trait-blok in één keer herschrijft. DowngradeCloneWithRector richt zich op [FileNode::class, Return_::class, Expression::class] omdat clone-with (clone($obj, [...])) zowel in return-posities als in toewijzingen kan voorkomen; deze regel gebruikt het bezoek aan FileNode om een teller per bestand te resetten.
Een regel die vanuit refactor() null retourneert, geeft „geen wijziging” aan. Een regel die een knoop retourneert, geeft een vervanging aan. Een regel die Stmt[] retourneert, een lijst met statements, breidt één statement uit tot meerdere. Zo zet DowngradeCloneWithRector één enkele return clone($this, [...]); om in een clone-toewijzing, één eigenschapstoewijzing per overschrijving en een afsluitende return.
Wat de ingebouwde sets al doen
Sectie met titel “Wat de ingebouwde sets al doen”De twee pijplijnconfiguraties roepen ->withDowngradeSets(php81: true) en ->withDowngradeSets(php74: true) aan. Die sets schakelen alle ingebouwde downgraderegels voor het target in. Aangepaste regels bestaan alleen voor de hiaten: asymmetrische zichtbaarheid in PHP 8.4, clone-with in PHP 8.5 en traitconstanten in PHP 8.2. Rector downgradet geen van deze functies uit zichzelf. Schrijf pas een aangepaste regel nadat u hetzelfde hiaat hebt bevestigd.
Stap voor stap: een regel schrijven
Sectie met titel “Stap voor stap: een regel schrijven”Deze procedure voegt een nieuwe regel toe. Het doorlopende voorbeeld weerspiegelt de bestaande regels; vervang dit door uw eigen functie.
- Maak de regelklasse aan in
rector/rules/. Geef deze de naamDowngrade<Feature>Rectoren plaats deze in de namespaceNextPDF\Backportzodat de bestaande autoload-mapping deze oppikt. - Breid
Rector\Rector\AbstractRectoruit en markeer de klasse alsfinal. - Implementeer
getNodeTypes()zodat deze de smalst mogelijke set AST-knoopklassen retourneert die de regel nodig heeft. Met een smallere set bezoekt Rector minder knopen. - Implementeer
refactor(). Controleer met een assertie dat de knoop een van de gedeclareerde typen is, bewaak de exacte syntaxis waarop u zich richt, transformeer die syntaxis en retourneer de nieuwe knoop,Stmt[]ofnullvoor geen wijziging. - Implementeer
getRuleDefinition()met één before/afterCodeSamplevoor elk afzonderlijk geval dat de regel afhandelt. - Houd het bestand op PHPStan Level 10: typeer elke parameter, retourwaarde en eigenschap, en gebruik PHPDoc-generics om de vorm van arrays te beschrijven.
De regel voor asymmetrische zichtbaarheid is het kleinste volledige voorbeeld. Deze regel verwijdert de set-zichtbaarheidsvlaggen en zorgt ervoor dat de basis-leeszichtbaarheid behouden blijft:
<?php
declare(strict_types=1);
namespace NextPDF\Backport;
use PhpParser\Modifiers;use PhpParser\Node;use PhpParser\Node\Param;use PhpParser\Node\Stmt\Property;use Rector\Rector\AbstractRector;use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample;use Symplify\RuleDocGenerator\ValueObject\RuleDefinition;
final class DowngradeAsymmetricVisibilityRector extends AbstractRector{ public function getRuleDefinition(): RuleDefinition { return new RuleDefinition( 'Remove asymmetric visibility modifiers (public private(set) -> public)', [ new CodeSample( 'public private(set) float $x = 0.0;', 'public float $x = 0.0;', ), ], ); }
/** * @return array<class-string<Node>> */ public function getNodeTypes(): array { return [Property::class, Param::class]; }
public function refactor(Node $node): ?Node { \assert($node instanceof Property || $node instanceof Param);
if (($node->flags & Modifiers::VISIBILITY_SET_MASK) === 0) { return null; }
$node->flags &= ~Modifiers::VISIBILITY_SET_MASK;
if (($node->flags & Modifiers::VISIBILITY_MASK) === 0) { $node->flags |= Modifiers::PUBLIC; }
return $node; }}De controle in de eerste if is cruciaal. Wanneer de eigenschap geen set-zichtbaarheidsvlag heeft, retourneert de regel null en laat deze de knoop ongewijzigd. Een regel die onvoorwaardelijk transformeert, zou code herschrijven die ongemoeid had moeten blijven.
Fixtures en testen
Sectie met titel “Fixtures en testen”Elke regel heeft een fixturegebaseerde test die de regel uitvoert op .php.inc-bestanden en controleert of de uitvoer overeenkomt. De harness komt uit Rector\Testing\PHPUnit\AbstractRectorTestCase van Rector.
Een testcase is klein en consistent voor de drie bestaande regels:
<?php
declare(strict_types=1);
namespace NextPDF\Backport\Tests\Rector;
use Iterator;use PHPUnit\Framework\Attributes\DataProvider;use Rector\Testing\PHPUnit\AbstractRectorTestCase;
final class DowngradeTraitConstantsRectorTest extends AbstractRectorTestCase{ #[DataProvider('provideData')] public function test(string $filePath): void { $this->doTestFile($filePath); }
public static function provideData(): Iterator { return self::yieldFilesFromDirectory(__DIR__ . '/Fixtures/DowngradeTraitConstants'); }
public function provideConfigFilePath(): string { return __DIR__ . '/config/downgrade_trait_constants.php'; }}De test verwijst naar een configuratiebestand per regel in tests/Rector/config/ dat alleen de regel onder test registreert, zodat elke fixture één regel geïsoleerd uitvoert:
<?php
declare(strict_types=1);
use NextPDF\Backport\DowngradeTraitConstantsRector;use Rector\Config\RectorConfig;
return RectorConfig::configure() ->withRules([ DowngradeTraitConstantsRector::class, ]);Een fixture is een .php.inc-bestand met de invoer, een ------scheidingsteken en de verwachte uitvoer. Wanneer de regel geen wijziging aanbrengt, laat u het scheidingsteken en het tweede blok weg. Een transformerende fixture voor de traitconstantenregel ziet er als volgt uit:
<?php
trait HasLimit{ private const MAX_SIZE = 1024;
public function getLimit(): int { return self::MAX_SIZE; }}?>-----<?php
trait HasLimit{ private static $MAX_SIZE = 1024;
public function getLimit(): int { return self::$MAX_SIZE; }}?>Om de tests voor een nieuwe regel te schrijven:
- Maak
tests/Rector/Fixtures/Downgrade<Feature>/aan en voeg één.php.incper geval toe. - Dek zowel transformerende gevallen met een
------scheidingsteken als overgeslagen gevallen zonder scheidingsteken af, zoals een eigenschap zonder set-zichtbaarheid die ongewijzigd moet blijven. - Voeg een
tests/Rector/config/downgrade_<feature>.phptoe die alleen uw regel registreert. - Voeg een
tests/Rector/Downgrade<Feature>RectorTest.phptoe die de fixturemap oplevert en naar de configuratie verwijst. - Voer de testsuite uit.
composer testDe repository bevat ook RectorRulesBehaviorTest en RectorRulesMetadataTest, die het gedrag over regels heen controleren en bevestigen dat de getRuleDefinition() van elke regel correct is opgebouwd. Voer de volledige composer test uit, zodat die controles uw nieuwe regel meenemen.
Inbouwen in de build
Sectie met titel “Inbouwen in de build”Een regel is pas actief in de build wanneer u deze registreert in de pijplijnconfiguraties. Er zijn twee buildtargets, elk met een eigen config in rector/config/.
- Open
rector/config/rector-php81.phpen voeg uw regelklasse toe aan de lijst->withRules([...]). - Als de functie ook moet worden gedowngraded voor de PHP 7.4-kernbuild, voeg dezelfde klasse dan toe aan
rector/config/rector-php74.php. - Voeg een commentaar toe met de PHP-versie waarin de functie is geïntroduceerd, in lijn met de bestaande vermeldingen.
<?php
declare(strict_types=1);
use NextPDF\Backport\DowngradeAsymmetricVisibilityRector;use NextPDF\Backport\DowngradeCloneWithRector;use NextPDF\Backport\DowngradeTraitConstantsRector;use Rector\Config\RectorConfig;use Rector\DowngradePhp81\Rector\Property\DowngradeReadonlyPropertyRector;
return RectorConfig::configure() ->withDowngradeSets(php81: true) ->withRules([ // PHP 8.4 — asymmetric visibility (not covered by built-in sets) DowngradeAsymmetricVisibilityRector::class,
// PHP 8.4 — clone-with syntax (not covered by built-in sets) DowngradeCloneWithRector::class,
// PHP 8.2 — trait constants (not covered by built-in sets) DowngradeTraitConstantsRector::class,
// PHP 8.1 — readonly property removal (for clone-with expansion safety) DowngradeReadonlyPropertyRector::class, ]);De buildorkestrator (scripts/build.php) voegt de bronrepository’s samen, draait Rector met deze configs, past de gegenereerde composer.json aan en voert een php -l-syntaxiscontrole uit op de uitvoer. Controleer uw regel met PHPStan en de volledige build voordat u erop vertrouwt.
composer analysecomposer build:dryRandgevallen en valkuilen
Sectie met titel “Randgevallen en valkuilen”- De registratievolgorde maakt niet uit, maar de regelvolgorde conceptueel wel. Het multi-passmechanisme van Rector doorloopt de boom opnieuw totdat geen enkele regel nog een wijziging aanbrengt, dus u hoeft regels niet handmatig in de config te ordenen. Documenteer toch elke volgordeafhankelijkheid in het docblock van de klasse, zoals
DowngradeCloneWithRectordoet: de expansie ervan produceert$clone->prop = $val, wat op een readonly-eigenschap zou mislukken, dusDowngradeReadonlyPropertyRectormoet voor hetzelfde target draaien. - Geef lege attributen door wanneer u een vervangende knoop bouwt vanuit een ander knooptype.
DowngradeTraitConstantsRectorbouwt eenPropertyuit eenClassConsten geeft[]door als attributen in plaats van de attributen van de bronknoop. Als u de oorspronkelijke attributen overneemt, laat u eenorigNode-verwijzing naar het verkeerde knooptype achter en activeert u een assertie in de opmaakbehoudende printer. - Reset de status per bestand bij het bezoek aan
FileNode.DowngradeCloneWithRectordeclareertFileNode::classingetNodeTypes()alleen om de teller voor tijdelijke variabelen aan het begin van elk bestand te resetten, zodat gegenereerde variabelenamen niet tussen bestanden botsen. - Bewaak nauwkeurig en retourneer dan
null. Een clone-with-transformatie moet bevestigen dat de aanroepnaamcloneis en dat het tweede argument een array-literal is voordat deze handelt; een gewoneclone $objbereikt de regel nooit als functieaanroep, en een aanroep met twee argumenten waarvan het tweede argument geen array is, blijft ongemoeid. - Verwijder modifiers die het target niet kan uitdrukken. Wanneer de traitconstantenregel een constante omzet in een statische eigenschap, behoudt deze de zichtbaarheid en voegt
statictoe, maar mag deze geenfinal-modifier dragen omdat PHP 8.1-eigenschappen niet final kunnen zijn. - Houd de regel op PHPStan Level 10. De repository draait
composer analyseop niveau 10 overrector/rulesenscripts. Typeer elke signature en annoteer arrayvormen; een regel die de analyser niet zou overleven, is een defect, geen concept.
Zie ook
Sectie met titel “Zie ook”- Ontwikkelaarshandleiding voor Backport Builder — de pijplijnarchitectuur, het branchmodel en de release-artefacten rond deze regels.
- Backport API-referentie — het gepubliceerde oppervlak voor de backport-buildtooling.
- Backport-configuratie — buildtargets en selectie van downgradesets.
- Backport-probleemoplossing — hoe u fouten in een gegenereerde downgradeboom diagnosticeert.
- Rector-documentatie — de upstream-referentie voor
AbstractRector,RuleDefinitionen de AST-knoopklassen die ingetNodeTypes()worden gebruikt.