Refactoring von Legacy-Code in PHP: Fortgeschrittenes Niveau
Die Arbeit mit veraltetem (Legacy-)Code in PHP ist für viele Entwickler eine unvermeidliche Realität. Oft handelt es sich dabei um eine monolithische Anwendung ohne Tests, mit globalen Abhängigkeiten und einer Vermischung von Geschäftslogik und Darstellung. Ein einfaches Refactoring „auf Sicht“ kann zu fatalen Fehlern führen. In diesem Artikel betrachten wir fortgeschrittene Refactoring-Techniken, die eine sichere und effiziente Modernisierung von Legacy-Systemen ermöglichen.
1. Die „Seam“-Strategie: Schaffung von Bruchstellen
Der erste Schritt beim Refactoring von Legacy-Code ist die Schaffung von Nähten (seams). Ein Naht ist eine Stelle, an der Sie das Verhalten des Codes ändern können, ohne ihn direkt zu bearbeiten. Dies ist besonders wichtig, wenn der Code nicht durch Tests abgedeckt ist und Sie ihn nicht isoliert ausführen können.
Betrachten wir ein klassisches Beispiel: die direkte Arbeit mit einer Datenbank innerhalb einer Methode.
<?php// Legacy-Codeclass UserReport { public function getActiveUsers() { $db = new PDO('mysql:host=localhost;dbname=test', 'root', ''); $stmt = $db->query(\"SELECT * FROM users WHERE active = 1\"); return $stmt->fetchAll(); }}?>Problem: new PDO(...) erzeugt eine feste Abhängigkeit. Um diese Methode zu testen, benötigt man eine echte Datenbank.
Lösung: Abhängigkeitsinjektion über den Konstruktor.
<?php// Refactoring: Naht durch DI schaffenclass UserReport { private PDO $pdo;
public function __construct(PDO $pdo) { $this->pdo = $pdo; }
public function getActiveUsers(): array { $stmt = $this->pdo->query(\"SELECT * FROM users WHERE active = 1\"); return $stmt->fetchAll(); }}
// Verwendung$pdo = new PDO('mysql:host=localhost;dbname=test', 'root', '');$report = new UserReport($pdo);?>Jetzt können Sie das PDO in Tests durch ein Mock-Objekt ersetzen. Dies ist eine einfache, aber effektive Naht.
2. Das „Sprout Method“- und „Sprout Class“-Pattern
Wenn Sie eine bestehende Methode nicht ändern können (z. B. aufgrund fehlender Tests), verwenden Sie die „Sprout“-Strategie – erstellen Sie neuen Code neben dem alten und schalten Sie die Aufrufe schrittweise um.
Beispiel: Wir haben eine riesige Methode, die einen Benutzer validiert und speichert.
<?phpclass UserService { public function saveUser(array $data): bool { // 100 Zeilen Validierung, Logging und Speicherung $email = $data['email'] ?? ''; if (!filter_var($email, FILTER_VALIDATE_EMAIL)) { throw new InvalidArgumentException('Invalid email'); } // ... viel anderer Code $db = new PDO(...); $stmt = $db->prepare(\"INSERT INTO users ...\"); // ... }}?>Anstatt die gesamte Methode auf einmal zu refaktorisieren, erstellen Sie eine neue Klasse für die Validierung.
<?phpclass UserValidator { public function validate(array $data): void { if (!filter_var($data['email'] ?? '', FILTER_VALIDATE_EMAIL)) { throw new InvalidArgumentException('Invalid email'); } // Fügen Sie weitere Prüfungen hinzu }}
class UserService { private UserValidator $validator;
public function __construct(UserValidator $validator) { $this->validator = $validator; }
public function saveUser(array $data): bool { $this->validator->validate($data); // Der restliche Code bleibt vorerst bestehen }}?>Jetzt können Sie Unittests für UserValidator schreiben, ohne den alten Code anzurühren. Schrittweise werden Sie die Logik aus saveUser in neue Klassen auslagern.
3. Beseitigung globaler Zustände und statischer Elemente
Legacy-Code verwendet oft globale Variablen, Singletons und statische Methoden. Dies macht den Code praktisch nicht te