von PHP 7.4 zu PHP 8.4 ohne manuelles Suchen-und-Ersetzen
Rector automatisiertes Refactoring ersetzt manuelles Suchen-und-Ersetzen bei PHP-Versionswechseln durch AST-basierte Codeumschreibung: Constructor Property Promotion, readonly-Properties, match-Expressions und der Nullsafe-Operator werden mechanisch und wiederholbar auf eine ganze Codebasis angewendet, inklusive Dry-Run-Vorschau, eigenen Regeln und CI-Integration gegen Regressionen.
Inhaltsverzeichnis
- 1. Was Rector ist und wie es sich von PHPStan und PHP-CS-Fixer unterscheidet
- 2. rector.php konfigurieren: Regelsets, LevelSetList und das PHP-8.4-Set
- 3. Dry-Run vs. Apply-Modus: sichere Ausführung
- 4. Konkrete Transformation: von PHP 7.4 zu PHP 8.4
- 5. Dead-Code-Entfernung und Type-Declaration-Sets
- 6. Eine eigene Rector-Regel schreiben
- 7. Große automatisierte Diffs sicher ausrollen
- 8. CI-Integration: Regressionen gegen alte Patterns verhindern
- 9. Rector im Vergleich zu manuellem Refactoring
- 10. Zusammenfassung
- 11. FAQ
1. Was Rector ist und wie es sich von PHPStan und PHP-CS-Fixer unterscheidet
Rector automatisiertes Refactoring bedeutet, dass ein Werkzeug PHP-Code nicht als Text, sondern als abstrakten Syntaxbaum (AST) versteht und gezielt umschreibt. Rector parst jede Datei mit nikic/php-parser in einen AST, wendet eine oder mehrere Regeln auf einzelne Knoten an und schreibt den veränderten Baum wieder als lesbaren PHP-Code zurück. Der entscheidende Unterschied zu einem Suchen-und-Ersetzen-Skript: Rector kennt Typen, Scope und Kontext eines Knotens, bevor es ihn verändert, und trifft dadurch Entscheidungen, die ein reines Textmuster nicht treffen kann.
Rector wird häufig mit PHPStan und PHP-CS-Fixer verwechselt, löst aber ein anderes Problem. PHPStan analysiert Code statisch und meldet Probleme, verändert aber keine einzige Zeile, die Behebung bleibt Handarbeit. PHP-CS-Fixer normalisiert Formatierung, Einrückung und Klammerstellung, rührt aber nicht an der Logik oder Struktur des Codes. Rector automatisiertes Refactoring geht einen Schritt weiter: Es schreibt die eigentliche Struktur um, etwa wenn eine switch-Anweisung durch eine match-Expression ersetzt oder ein klassischer Konstruktor in Constructor Property Promotion überführt wird.
Für Teams, die eine Codebasis von PHP 7.4 auf PHP 8.4 heben müssen, ist genau das der entscheidende Vorteil. Statt hunderte Dateien manuell zu durchsuchen und Muster einzeln zu ersetzen, wendet Rector die passenden Regeln mechanisch und wiederholbar auf die gesamte Codebasis an. Das Ergebnis ist deterministisch: Derselbe Input erzeugt immer denselben Output, was bei manuellen Änderungen durch mehrere Entwickler kaum zu garantieren ist.
2. rector.php konfigurieren: Regelsets, LevelSetList und das PHP-8.4-Set
Der Ausgangspunkt für Rector automatisiertes Refactoring ist die Datei rector.php im Projektroot. Sie gibt ein RectorConfig-Objekt zurück, das über eine Fluent-Interface-API konfiguriert wird: withPaths() legt fest, welche Verzeichnisse verarbeitet werden, withSkip() schließt einzelne Dateien oder Regeln aus, und withPhpSets() aktiviert das Regelset für eine bestimmte PHP-Zielversion. In älteren Rector-Versionen übernahm die Klasse LevelSetList diese Aufgabe über withSets([LevelSetList::UP_TO_PHP_84]), die aktuelle API bündelt das kompakter in withPhpSets(php84: true).
Neben dem PHP-8.4-Set gibt es vorbereitete Sets für Dead-Code-Entfernung (deadCode), Type-Declaration-Vervollständigung (typeDeclarations) und Sichtbarkeits-Verschärfung (privatization), die über withPreparedSets() gemeinsam aktiviert werden. Wer zusätzlich eine projektspezifische Regel anwenden will, hängt sie über withRules() an, ohne das restliche Set zu verändern. Diese Kombinierbarkeit ist einer der Gründe, warum Rector automatisiertes Refactoring sich in bestehende Projekte integrieren lässt, ohne dass man sofort ein komplettes Regelwerk übernehmen muss.
<?php
declare(strict_types=1);
use Rector\Config\RectorConfig;
// rector.php - main configuration entry point
return RectorConfig::configure()
->withPaths([
__DIR__ . '/src',
__DIR__ . '/tests',
])
->withSkip([
__DIR__ . '/src/Legacy/OldBootstrap.php',
// Skip a single rule for a specific path only
\Rector\CodeQuality\Rector\If_\ExplicitBoolCompareRector::class => [
__DIR__ . '/src/Legacy',
],
])
// Target rule set: rewrite constructs up to PHP 8.4
->withPhpSets(php84: true)
// Additional prepared sets: dead code, type declarations, visibility
->withPreparedSets(
deadCode: true,
typeDeclarations: true,
privatization: true,
)
->withRules([
\Rector\CodingStyle\Rector\Class_\AddArrayDefaultToArrayPropertyRector::class,
])
->withCache(cacheDirectory: __DIR__ . '/var/rector-cache');
3. Dry-Run vs. Apply-Modus: sichere Ausführung
Rector automatisiertes Refactoring kennt zwei grundlegende Ausführungsmodi: den Dry-Run- und den Apply-Modus. Im Dry-Run-Modus (--dry-run) analysiert Rector jede Datei, wendet die konfigurierten Regeln gedanklich an und zeigt den resultierenden Diff im Terminal an, ohne eine einzige Datei zu verändern. Das ist der sichere Standardweg, um vor jeder größeren Änderung zu sehen, wie viele Dateien betroffen wären und wie die konkreten Änderungen aussehen, bevor überhaupt etwas geschrieben wird.
Erst wenn der Dry-Run-Diff geprüft und für sinnvoll befunden wurde, folgt der Apply-Modus ohne das Flag, der die Änderungen tatsächlich in die Dateien schreibt. Rector cacht dabei den Parse-Zustand jeder Datei anhand eines Hash-Werts, um wiederholte Läufe zu beschleunigen. Nach einer Änderung an der rector.php-Konfiguration selbst kann dieser Cache veraltete Ergebnisse liefern, weshalb --clear-cache in solchen Fällen Pflicht ist, um den Cache explizit zu invalidieren und einen sauberen Neustart zu erzwingen.
# Preview changes without touching any file
vendor/bin/rector process --dry-run
# After a config change: invalidate the file-hash cache first
vendor/bin/rector process --dry-run --clear-cache
# Review the proposed diff manually
git diff --stat
# Apply the reviewed changes for real
vendor/bin/rector process
# Run the test suite immediately to confirm behavior is unchanged
vendor/bin/phpunit
# Stage and commit in reviewable chunks
git add -p
git commit -m "Rector: apply PHP 8.4 rule set to src/Domain"
4. Konkrete Transformation: von PHP 7.4 zu PHP 8.4
Ein konkretes Beispiel zeigt, wie tiefgreifend Rector automatisiertes Refactoring einen Klassenkörper verändern kann. Eine typische PHP-7.4-Klasse deklariert private Properties, weist sie im Konstruktor Zeile für Zeile zu, nutzt eine switch-Anweisung zur Statusauswertung und prüft verschachtelt auf null, bevor auf ein verschachteltes Objekt zugegriffen wird. Jede dieser drei Stellen hat in PHP 8.0 bis 8.4 eine kürzere, sicherere Entsprechung, die Rector automatisch erkennt und einsetzt.
Die entsprechende PHP-8.4-Regel ersetzt die manuelle Zuweisung im Konstruktor durch Constructor Property Promotion mit readonly-Modifier, verwandelt die switch-Anweisung in eine match-Expression mit erzwungener Vollständigkeit, und ersetzt die verschachtelte Null-Prüfung durch den Nullsafe-Operator ?->. Aufrufer der Klasse können zusätzlich von Named Arguments profitieren, etwa new OrderProcessor(logger: $logger, currency: 'EUR'), was Rector zwar nicht erzwingt, aber durch die promovierten, klar benannten Parameter erst praktikabel macht.
// BEFORE: PHP 7.4 style
class OrderProcessor
{
private LoggerInterface $logger;
private PaymentGatewayInterface $gateway;
private string $currency;
public function __construct(
LoggerInterface $logger,
PaymentGatewayInterface $gateway,
string $currency
) {
$this->logger = $logger;
$this->gateway = $gateway;
$this->currency = $currency;
}
public function statusLabel(int $status): string
{
switch ($status) {
case self::STATUS_NEW:
$label = 'new';
break;
case self::STATUS_PAID:
$label = 'paid';
break;
case self::STATUS_SHIPPED:
$label = 'shipped';
break;
default:
$label = 'unknown';
}
return $label;
}
public function customerCity(?Customer $customer): ?string
{
if ($customer === null) {
return null;
}
$address = $customer->getAddress();
if ($address === null) {
return null;
}
return $address->getCity();
}
}
// AFTER: PHP 8.4 style, generated by Rector
final class OrderProcessor
{
public function __construct(
private readonly LoggerInterface $logger,
private readonly PaymentGatewayInterface $gateway,
private readonly string $currency,
) {
}
public function statusLabel(int $status): string
{
return match ($status) {
self::STATUS_NEW => 'new',
self::STATUS_PAID => 'paid',
self::STATUS_SHIPPED => 'shipped',
default => 'unknown',
};
}
public function customerCity(?Customer $customer): ?string
{
return $customer?->getAddress()?->getCity();
}
}
5. Dead-Code-Entfernung und Type-Declaration-Sets
Neben reinen Versions-Upgrades deckt Rector automatisiertes Refactoring auch Aufräumarbeiten ab, die in gewachsenen Codebasen liegen bleiben. Das deadCode-Set erkennt zum Beispiel private Methoden, die nirgends aufgerufen werden, Properties, die nie gelesen werden, und Bedingungen, die aufgrund bekannter Typen nie wahr werden können. Solche toten Pfade werden nicht nur gemeldet wie bei PHPStan, sondern direkt entfernt, sofern das Entfernen nachweislich sicher ist.
Das typeDeclarations-Set ergänzt fehlende Parameter-, Rückgabe- und Property-Typen anhand von Docblocks, Default-Werten und Aufrufkontext. Eine Methode, deren Rückgabetyp bisher nur im @return-Kommentar stand, bekommt die native Typdeklaration im Methodenkopf. Das privatization-Set verschärft Sichtbarkeiten, wo eine Property oder Methode nur innerhalb der eigenen Klasse verwendet wird, und macht sie von public oder protected zu private. Zusammen reduzieren diese Sets die Angriffsfläche und die kognitive Last beim Lesen einer Klasse spürbar.
6. Eine eigene Rector-Regel schreiben
Die vorgefertigten Sets decken die meisten Standardfälle ab, aber projektspezifische Muster erfordern eine eigene Regel. Rector automatisiertes Refactoring erlaubt genau das über die Klasse AbstractRector: Eine eigene Regel implementiert getNodeTypes(), um zu deklarieren, welche AST-Knotentypen sie besuchen will, und refactor(), um den eigentlichen Umbau vorzunehmen. Gibt refactor() null zurück, bleibt der Knoten unverändert, gibt es den veränderten Knoten zurück, ersetzt Rector ihn im Baum.
Die Methode getRuleDefinition() liefert eine maschinenlesbare Beschreibung mit Vorher-Nachher-Codebeispiel, die Rector sowohl für die automatisch generierte Dokumentation als auch für Tests der eigenen Regel nutzt. Ein typischer Anwendungsfall: Ein internes Logger-Interface benennt eine veraltete Methode warn() in warning() um, und statt diesen Aufruf in hundert Dateien manuell zu suchen, übernimmt eine kleine, getestete Rector-Regel die Umbenennung projektweit.
<?php
declare(strict_types=1);
namespace App\Rector;
use PhpParser\Node;
use PhpParser\Node\Expr\MethodCall;
use PhpParser\Node\Identifier;
use Rector\Rector\AbstractRector;
use Symplify\RuleDocGenerator\ValueObject\CodeSample;
use Symplify\RuleDocGenerator\ValueObject\RuleDefinition;
/**
* Custom rule: renames deprecated Logger::warn() calls to Logger::warning()
*/
final class RenameLegacyLoggerMethodRector extends AbstractRector
{
public function getRuleDefinition(): RuleDefinition
{
return new RuleDefinition(
'Renames deprecated Logger::warn() calls to Logger::warning()',
[
new CodeSample(
'$logger->warn("message");',
'$logger->warning("message");'
),
]
);
}
public function getNodeTypes(): array
{
return [MethodCall::class];
}
public function refactor(Node $node): ?Node
{
if (! $node instanceof MethodCall) {
return null;
}
if (! $this->isName($node->name, 'warn')) {
return null;
}
$node->name = new Identifier('warning');
return $node;
}
}
7. Große automatisierte Diffs sicher ausrollen
Ein einzelner Rector-Lauf über eine große Legacy-Codebasis kann tausende Zeilen in einem einzigen Diff verändern, was Code-Reviews praktisch unmöglich macht. Der sichere Weg besteht darin, Rector automatisiertes Refactoring in kleinen, nachvollziehbaren Schritten auszuführen: withPaths() auf ein einzelnes Modul oder Verzeichnis begrenzen, den Dry-Run-Diff für genau diesen Ausschnitt prüfen, anwenden, testen, committen, und erst danach zum nächsten Verzeichnis übergehen.
Nach jedem Chunk sollte die vollständige Testsuite laufen, nicht nur ein Teilausschnitt, weil Rector-Regeln gelegentlich Randfälle berühren, die in unabhängigen Tests sichtbar werden. Bei Konfigurationsänderungen an der rector.php-Datei selbst ist --clear-cache Pflicht, da der Datei-Hash-Cache sonst veraltete Analyseergebnisse liefert und Änderungen unterschlägt. Ein Feature-Branch pro Chunk mit eigenem Pull Request hält die Diffs überschaubar und erlaubt es Reviewern, sich auf semantische statt auf mechanische Änderungen zu konzentrieren.
8. CI-Integration: Regressionen gegen alte Patterns verhindern
Ohne Absicherung in der CI-Pipeline schleichen sich alte Muster nach einem einmaligen Rector-Lauf schnell wieder ein: Ein neuer Pull Request fügt eine klassische switch-Anweisung statt einer match-Expression hinzu, oder ein Entwickler schreibt einen Konstruktor ohne Property Promotion. Rector automatisiertes Refactoring lässt sich genau dafür in die CI-Pipeline integrieren, indem derselbe Befehl, der lokal für die Migration genutzt wurde, als Prüfschritt mit --dry-run läuft.
Meldet Rector im Dry-Run-Modus einen ausstehenden Diff, bedeutet das, dass mindestens eine Datei vom konfigurierten Regelsatz abweicht, und der Pipeline-Schritt sollte mit einem Nicht-Null-Exit-Code fehlschlagen. So verhindert Rector automatisiertes Refactoring nicht nur die einmalige Migration, sondern auch den schleichenden Rückfall in alte Patterns, ganz ohne dass ein Reviewer jede switch-Anweisung von Hand suchen muss.
name: rector-check
on: [pull_request]
jobs:
rector:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: shivammathur/setup-php@v2
with:
php-version: '8.4'
- run: composer install --no-interaction --prefer-dist
# Fail the build if any file still needs a Rector rewrite
- run: vendor/bin/rector process --dry-run --clear-cache
- run: vendor/bin/phpunit
9. Rector im Vergleich zu manuellem Refactoring
Rector, PHPStan und PHP-CS-Fixer werden in Projekten oft gemeinsam eingesetzt, weil sie unterschiedliche Ebenen desselben Qualitätsproblems abdecken. PHP-CS-Fixer normalisiert ausschließlich die Formatierung, PHPStan findet Typ- und Logikfehler, ohne sie zu beheben, und Rector ist das einzige der drei Werkzeuge, das die Struktur des Codes tatsächlich umschreibt. Die folgende Tabelle stellt Rector automatisiertes Refactoring dem klassischen manuellen Refactoring direkt gegenüber.
| Kriterium | Manuelles Refactoring | Rector automatisiertes Refactoring | Effekt |
|---|---|---|---|
| Geschwindigkeit | Tage bis Wochen bei tausenden Dateien | Minuten für die gesamte Codebasis | Migration wird planbar statt aufwandsabhängig |
| Konsistenz | Abhängig von Person und Tagesform | Deterministisch, dieselbe Regel überall | Kein Stilbruch zwischen Modulen |
| Risiko bei großen Diffs | Hoch, schwer reviewbar | Reduzierbar durch Chunking und Tests | Reviewbare, nachvollziehbare Schritte |
| Reversibilität | Kein einheitliches Rollback-Muster | Git-Commit pro Chunk, klar revertierbar | Einzelne Regeln lassen sich isoliert zurücknehmen |
| Skalierung auf Legacy-Code | Sinkt mit Projektgröße rapide | Bleibt bei wachsender Codebasis konstant | Auch Codebasen mit Millionen Zeilen migrierbar |
Der Geschwindigkeitsunterschied wird besonders bei Versions-Upgrades sichtbar: Wo ein Team für eine manuelle Migration von PHP 7.4 auf PHP 8.4 Wochen einplant, verarbeitet Rector dieselbe Codebasis in Minuten und liefert dabei einen reproduzierbaren, review-baren Diff statt einer Reihe unabhängiger, potenziell inkonsistenter Handänderungen.
10. Zusammenfassung
Rector automatisiertes Refactoring löst ein Problem, das reines Suchen-und-Ersetzen und reine Analyse-Tools wie PHPStan nicht lösen können: die mechanische, AST-basierte Umschreibung von Code über eine ganze Codebasis hinweg. Die rector.php-Konfiguration mit withPhpSets() und withPreparedSets() bündelt Regeln für PHP-Versions-Upgrades, Dead-Code-Entfernung und Typdeklarationen in einer einzigen, versionierbaren Datei. Der Dry-Run-Modus macht jede Änderung vor der Anwendung sichtbar, während --clear-cache verhindert, dass ein veralteter Cache falsche Ergebnisse liefert.
Eigene Rector-Regeln über AbstractRector erweitern das Werkzeug um projektspezifische Refactorings, die kein vorgefertigtes Set abdeckt. Große Migrationen laufen sicher in kleinen, getesteten Chunks statt in einem einzigen riesigen Diff, und die Integration von Rector automatisiertes Refactoring in die CI-Pipeline mit --dry-run verhindert, dass alte Muster nach der Migration schleichend zurückkehren. Zusammen ersetzt das eine Disziplin, die früher ausschließlich auf manueller Sorgfalt beruhte, durch einen wiederholbaren, automatisierten Prozess.
Rector automatisiertes Refactoring, das Wichtigste auf einen Blick
Konfiguration
rector.php mit withPhpSets() und withPreparedSets() bündelt PHP-8.4-Regeln, Dead-Code-Entfernung und Typdeklarationen in einer Datei.
Sichere Ausführung
Dry-Run vor jedem Apply, --clear-cache nach Config-Änderungen, vollständige Tests nach jedem Chunk.
Custom Rules
AbstractRector mit getNodeTypes() und refactor() für projektspezifische Refactorings, die kein Set abdeckt.
CI-Integration
rector --dry-run als Pipeline-Schritt verhindert den Rückfall in alte Patterns nach der Migration.
11. FAQ: Rector automatisiertes Refactoring
1Was ist Rector automatisiertes Refactoring genau?
2Wie unterscheidet sich Rector von PHPStan?
3Wie unterscheidet sich Rector von PHP-CS-Fixer?
4Dry-Run vs. Apply-Modus?
5Wozu dient --clear-cache?
6Wie schreibe ich eine eigene Rector-Regel?
7Große Diffs sicher ausrollen?
8Kann Rector Bugs einführen?
9Wie integriere ich Rector in CI?
10Ersetzt Rector Code-Reviews?
Mironsoft
PHP-Versionsupgrades, Legacy-Modernisierung und CI-Absicherung
Eine Codebasis auf PHP 8.4 heben, ohne wochenlange Handarbeit?
Wir konfigurieren rector.php für euer Projekt, schreiben projektspezifische Custom-Regeln und rollen große automatisierte Diffs sicher in kleinen, getesteten Schritten aus, inklusive CI-Absicherung gegen den Rückfall in alte Patterns.
Konfiguration
rector.php mit passenden PHP-8.4-Regelsets und projektspezifischen Skip-Regeln aufsetzen
Custom Rules
Eigene Rector-Regeln für projektspezifische Refactorings entwickeln und testen
CI-Integration
rector --dry-run in die Pipeline integrieren und Regressionen dauerhaft verhindern