Eine eigene Rector-Downgrade-Regel erstellen
Auf einen Blick
Abschnitt betitelt „Auf einen Blick“Die NextPDF-Backport-Pipeline führt für den PHP-8.4-Quellcode von nextpdf/nextpdf ein Downgrade durch, sodass er unter PHP 8.1 (und beim Core auch unter PHP 7.4) läuft. Für die meisten Sprachfunktionen verwendet sie Rectors eingebaute Downgrade-Sets sowie einige eigene Regeln für Funktionen, die Rector nicht von Haus aus abdeckt.
Diese Anleitung zeigt Ihnen, wie Sie eine neue eigene Regel hinzufügen. Sie folgt dem Aufbau der drei bereits im Repository vorhandenen Regeln: DowngradeAsymmetricVisibilityRector, DowngradeCloneWithRector und DowngradeTraitConstantsRector. Alle drei liegen in rector/rules/, sind in rector/config/ registriert und werden durch Fixture-basierte Tests in tests/Rector/ abgedeckt.
Verwenden Sie diese Anleitung, wenn ein NextPDF-Feature PHP-Syntax einsetzt, die vom Build-Ziel nicht unterstützt wird, und Rector kein eingebautes Downgrade dafür bietet. Stellen Sie vorab sicher, dass Rector dafür wirklich keine Regel enthält. Die eingebaute withDowngradeSets()-Kette behandelt bereits readonly-Klassen, typisierte Klassenkonstanten, den Pipe-Operator und viele weitere Funktionen.
Installation
Abschnitt betitelt „Installation“Eigene Regeln schreiben und führen Sie innerhalb des nextpdf-backport-Repositorys aus. Die Entwicklungswerkzeuge sind in dessen composer.json deklariert.
- Klonen Sie das Backport-Repository und installieren Sie seine Abhängigkeiten.
- Stellen Sie sicher, dass Rector und PHPUnit aufgelöst werden.
git clone https://github.com/nextpdf-labs/backport.gitcd backportcomposer installvendor/bin/rector --versionvendor/bin/phpunit --versionDas Repository benötigt für die Ausführung PHP 8.4 (die Regeln manipulieren 8.4-Syntaxbäume), auch wenn die Build-Ausgabe auf PHP 8.1 oder 7.4 zielt. In composer.jsonrequire ist php auf >=8.4 <9.0 festgelegt. Eigene Regeln werden über den Namespace NextPDF\Backport\ autogeladen, der auf rector/rules/ abgebildet ist; Tests werden über NextPDF\Backport\Tests\ autogeladen und auf tests/ abgebildet.
Aufbau einer Regel
Abschnitt betitelt „Aufbau einer Regel“Jede eigene Regel erweitert Rector\Rector\AbstractRector und implementiert drei Methoden. Der Vertrag ist über alle drei bestehenden Regeln hinweg derselbe.
| Element | Typ | Zweck |
|---|---|---|
getRuleDefinition() | RuleDefinition | Menschenlesbare Beschreibung sowie before/after-CodeSample-Paare für den Regeldokumentations-Generator. |
getNodeTypes() | array<class-string<Node>> | Die AST-Knotenklassen (Abstract Syntax Tree), die die Regel besuchen soll. Rector ruft refactor() nur für diese auf. |
refactor(Node $node) | ?Node oder `Stmt[] | null` |
Die Knotentyp-Deklaration steuert alles. DowngradeAsymmetricVisibilityRector zielt auf [Property::class, Param::class], weil asymmetrische Sichtbarkeit (public private(set)) sowohl an einer Klasseneigenschaft als auch an einem promoteten Konstruktorparameter auftreten kann. DowngradeTraitConstantsRector zielt auf [Trait_::class], weil es den gesamten Trait-Körper in einem Durchgang umschreibt. DowngradeCloneWithRector zielt auf [FileNode::class, Return_::class, Expression::class], weil clone-with (clone($obj, [...])) sowohl in return- als auch in Zuweisungspositionen vorkommt; den FileNode-Besuch nutzt es, um einen Zähler pro Datei zurückzusetzen.
Eine Regel, die null aus refactor() zurückgibt, signalisiert „keine Änderung“. Eine Regel, die einen Knoten zurückgibt, signalisiert eine Ersetzung. Eine Regel, die Stmt[] (eine Liste von Statements) zurückgibt, weitet ein Statement auf mehrere aus. So macht DowngradeCloneWithRector aus einem einzelnen return clone($this, [...]); eine Klon-Zuweisung, je eine Eigenschaftszuweisung pro Override und ein abschließendes return.
Was die eingebauten Sets bereits erledigen
Abschnitt betitelt „Was die eingebauten Sets bereits erledigen“Die beiden Pipeline-Konfigurationen rufen ->withDowngradeSets(php81: true) und ->withDowngradeSets(php74: true) auf. Diese Sets verketten alle eingebauten Downgrade-Regeln für das jeweilige Ziel. Eigene Regeln gibt es nur für die Lücken: asymmetrische Sichtbarkeit aus PHP 8.4, clone-with aus PHP 8.5 und Trait-Konstanten aus PHP 8.2. Rector downgradet keine davon von sich aus. Schreiben Sie eine eigene Regel erst, nachdem Sie dieselbe Lücke bestätigt haben.
Schritt für Schritt: eine Regel schreiben
Abschnitt betitelt „Schritt für Schritt: eine Regel schreiben“Dieses Verfahren fügt eine neue Regel hinzu. Das durchgängige Beispiel spiegelt den Aufbau der bestehenden Regeln wider; setzen Sie Ihr eigenes Feature ein.
- Erstellen Sie die Regelklasse in
rector/rules/. Benennen Sie sieDowngrade<Feature>Rectorund legen Sie sie im NamespaceNextPDF\Backportab, damit die bestehende Autoload-Abbildung sie erfasst. - Erweitern Sie
Rector\Rector\AbstractRectorund markieren Sie die Klasse alsfinal. - Implementieren Sie
getNodeTypes()so, dass es die engste Menge an AST-Knotenklassen zurückgibt, die die Regel benötigt. Eine engere Menge bedeutet, dass Rector weniger Knoten besucht. - Implementieren Sie
refactor(). Stellen Sie sicher, dass der Knoten einer der deklarierten Typen ist, sichern Sie genau die Syntax ab, auf die Sie zielen, transformieren Sie ihn und geben Sie den neuen Knoten zurück (oderStmt[]odernullbei keiner Änderung). - Implementieren Sie
getRuleDefinition()mit einem before/afterCodeSamplepro eigenständigem Fall, den die Regel behandelt. - Halten Sie die Datei auf PHPStan-Level 10: Jeder Parameter, jeder Rückgabewert und jede Eigenschaft ist typisiert, und PHPDoc-Generics beschreiben die Array-Formen.
Die Regel für asymmetrische Sichtbarkeit ist das kleinste vollständige Beispiel. Sie entfernt die Set-Sichtbarkeitsflags und stellt sicher, dass eine Basis-Lesesichtbarkeit erhalten bleibt:
<?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; }}Die Bedingung im ersten if ist der entscheidende Teil. Wenn die Eigenschaft kein Set-Sichtbarkeitsflag hat, gibt die Regel null zurück und lässt den Knoten unverändert. Eine Regel, die bedingungslos transformiert, würde Code umschreiben, den sie nicht anfassen sollte.
Fixtures und Tests
Abschnitt betitelt „Fixtures und Tests“Jede Regel hat einen Fixture-basierten Test, der die Regel gegen .php.inc-Dateien ausführt und prüft, ob die Ausgabe übereinstimmt. Der Harness stammt aus Rectors Rector\Testing\PHPUnit\AbstractRectorTestCase.
Ein Testfall ist kompakt und über die drei bestehenden Regeln hinweg einheitlich aufgebaut:
<?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'; }}Der Test verweist auf eine Konfigurationsdatei pro Regel in tests/Rector/config/, die nur die zu testende Regel registriert, sodass ein Fixture eine einzelne Regel isoliert durchläuft:
<?php
declare(strict_types=1);
use NextPDF\Backport\DowngradeTraitConstantsRector;use Rector\Config\RectorConfig;
return RectorConfig::configure() ->withRules([ DowngradeTraitConstantsRector::class, ]);Ein Fixture ist eine .php.inc-Datei mit der Eingabe, einem ------Trenner und der erwarteten Ausgabe. Wenn die Regel keine Änderung vornimmt, lassen Sie den Trenner und den zweiten Block weg. Ein transformierendes Fixture für die Trait-Konstanten-Regel sieht so aus:
<?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; }}?>So legen Sie die Tests einer neuen Regel an:
- Erstellen Sie
tests/Rector/Fixtures/Downgrade<Feature>/und fügen Sie pro Fall ein.php.inchinzu. - Decken Sie sowohl transformierende Fälle (mit einem
------Trenner) als auch Skip-Fälle (ohne Trenner) ab – zum Beispiel muss eine Eigenschaft ohne Set-Sichtbarkeit unverändert durchlaufen. - Fügen Sie eine
tests/Rector/config/downgrade_<feature>.phphinzu, die nur Ihre Regel registriert. - Fügen Sie eine
tests/Rector/Downgrade<Feature>RectorTest.phphinzu, die das Fixture-Verzeichnis liefert und auf die Konfiguration verweist. - Führen Sie die Suite aus.
composer testDas Repository enthält außerdem RectorRulesBehaviorTest und RectorRulesMetadataTest; sie prüfen regelübergreifendes Verhalten und stellen sicher, dass die getRuleDefinition() jeder Regel wohlgeformt ist. Führen Sie das vollständige composer test aus, damit diese Gates Ihre neue Regel erfassen.
In den Build einbinden
Abschnitt betitelt „In den Build einbinden“Eine Regel ist im Build nicht aktiv, solange sie nicht in den Pipeline-Konfigurationen registriert ist. Es gibt zwei Build-Ziele, jedes mit seiner eigenen Konfiguration in rector/config/.
- Öffnen Sie
rector/config/rector-php81.phpund fügen Sie Ihre Regelklasse zur->withRules([...])-Liste hinzu. - Wenn das Feature auch für den PHP-7.4-Core-Build downgegradet werden muss, fügen Sie dieselbe Klasse zu
rector/config/rector-php74.phphinzu. - Fügen Sie einen Kommentar hinzu, der die PHP-Version nennt, in der das Feature eingeführt wurde – passend zu den bestehenden Einträgen.
<?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, ]);Der Build-Orchestrator (scripts/build.php) führt die Quell-Repositorys zusammen, lässt Rector mit diesen Konfigurationen laufen, passt die generierte composer.json an und führt eine php -l-Syntaxprüfung über die Ausgabe aus. Prüfen Sie Ihre Regel gegen PHPStan und den vollständigen Build, bevor Sie sich darauf verlassen.
composer analysecomposer build:dryRandfälle & Fallstricke
Abschnitt betitelt „Randfälle & Fallstricke“- Die Registrierungsreihenfolge spielt keine Rolle, die Regelreihenfolge konzeptionell aber schon. Rectors Multi-Pass-Mechanismus durchläuft den Baum erneut, bis keine Regel mehr eine weitere Änderung vornimmt, sodass Sie die Regeln in der Konfiguration nicht von Hand ordnen müssen. Dokumentieren Sie trotzdem jede Reihenfolgenabhängigkeit im Klassen-Docblock, wie es
DowngradeCloneWithRectortut: Seine Expansion erzeugt$clone->prop = $val, was bei einer readonly-Eigenschaft fehlschlagen würde, sodassDowngradeReadonlyPropertyRectorfür dasselbe Ziel laufen muss. - Übergeben Sie leere Attribute, wenn Sie einen Ersatzknoten aus einer anderen Knotenart bauen.
DowngradeTraitConstantsRectorbaut einePropertyaus einerClassConstund übergibt[]als Attribute statt der Attribute des Quellknotens. Die ursprünglichen Attribute zu übernehmen würde einenorigNode-Zeiger auf die falsche Knotenart hinterlassen und eine Assertion im formaterhaltenden Printer auslösen. - Setzen Sie den Dateizustand beim
FileNode-Besuch zurück.DowngradeCloneWithRectordeklariertFileNode::classingetNodeTypes()allein dazu, seinen Zähler für temporäre Variablen zu Beginn jeder Datei zurückzusetzen, damit generierte Variablennamen nicht über Dateien hinweg kollidieren. - Sichern Sie präzise ab und geben Sie dann
nullzurück. Eine clone-with-Transformation muss prüfen, dass der Aufrufnameclonelautet und dass das zweite Argument ein Array-Literal ist, bevor sie eine Änderung vornimmt; ein einfachesclone $objerreicht die Regel nie als Funktionsaufruf, und ein zweiargumentiger Aufruf, dessen zweites Argument kein Array ist, bleibt unangetastet. - Entfernen Sie Modifikatoren, die das Ziel nicht ausdrücken kann. Wenn die Trait-Konstanten-Regel eine Konstante in eine statische Eigenschaft umwandelt, erhält sie die Sichtbarkeit und ergänzt
static, darf aber keinenfinal-Modifikator tragen, weil PHP-8.1-Eigenschaften nicht final sein können. - Halten Sie die Regel auf PHPStan-Level 10. Das Repository führt
composer analyseauf Level 10 überrector/rulesundscriptsaus. Typisieren Sie jede Signatur und annotieren Sie Array-Formen; eine Regel, die den Analyser nicht überstehen würde, ist ein Defekt, kein Entwurf.
Siehe auch
Abschnitt betitelt „Siehe auch“- Backport-Builder-Entwicklerhandbuch – die Pipeline-Architektur, das Branch-Modell und die Release-Artefakte rund um diese Regeln.
- Backport-API-Referenz – die öffentliche Schnittstelle der Backport-Build-Werkzeuge.
- Backport-Konfiguration – Build-Ziele und die Auswahl der Downgrade-Sets.
- Backport-Fehlerbehebung – die Diagnose von Fehlern in einem generierten Downgrade-Baum.
- Rector-Dokumentation – die Upstream-Referenz für
AbstractRector,RuleDefinitionund die ingetNodeTypes()verwendeten AST-Knotenklassen.