Eine ungepflegte Abhängigkeit wird nicht automatisch zu einem Vorfall, schränkt jedoch die Weiterentwicklungsfähigkeit einer Anwendung ein. Sie kann ein Update von PHP oder des Frameworks blockieren, nicht behobene Sicherheitslücken mit sich bringen, von veralteten Erweiterungen abhängen oder Datenformate erzwingen, die nicht mehr zu anderen Systemen passen. Das Problem ist nicht nur technisch: Jedes fragile Paket erhöht die Kosten und das Risiko, das Produkt zu ändern.
Das Ziel beim Ersetzen nicht mehr gepflegter PHP-Pakete sollte nicht darin bestehen, das gesamte Repository auf einmal zu modernisieren. Es geht darum, das Risiko nachweisbar zu reduzieren, die vom Geschäft benötigten Verhaltensweisen zu bewahren und die Möglichkeit zu behalten, jeden Schritt rückgängig zu machen.
Fehlende Pflege als Weiterentwicklungsrisiko behandeln

Ein Paket kann nicht mehr gepflegt sein, obwohl es in Produktion weiterhin funktioniert. Das relevante Signal ist nicht allein das Datum seiner letzten Änderung, sondern seine Fähigkeit, mit dem System Schritt zu halten. Es sollte bewertet werden, ob es Sicherheitskorrekturen erhält, ob es Kompatibilität mit der aktuellen PHP-Version erklärt, ob seine indirekten Abhängigkeiten die Weiterentwicklung blockieren oder ob das Team einen Fehler darin diagnostizieren kann.
Auch seine Position ist wichtig. Eine Formatierungsbibliothek, die in einer internen Aufgabe verwendet wird, hat ein anderes Profil als eine Komponente für Authentifizierung, Zahlungen, die Erstellung von Steuerdokumenten oder die Verarbeitung personenbezogener Daten. Die Priorität muss Ausfallwahrscheinlichkeit, geschäftliche Auswirkungen und Interventionskosten verbinden.
Nicht jede alte Abhängigkeit erfordert einen sofortigen Ersatz. Wenn sie isoliert ist, keine nicht vertrauenswürdigen Eingaben verarbeitet, ein stabiles Verhalten aufweist und notwendige Änderungen nicht blockiert, kann es sinnvoll sein, sie zu kapseln und ihre Entfernung zu planen. Dagegen erfordert eine dem Internet ausgesetzte Komponente oder eine Komponente, die ein Update der Laufzeitumgebung verhindert, eine frühere Entscheidung.
Ein Inventar erstellen, das Entscheidungen ermöglicht
Eine Auflistung von composer.json und composer.lock ist der Ausgangspunkt, nicht die Analyse. Ein nützliches Inventar identifiziert sowohl direkte als auch transitive Abhängigkeiten und beantwortet operative Fragen:
- Tatsächliche Nutzung: Welche Klassen, Befehle, Controller oder Prozesse rufen das Paket auf und wie häufig?
- Geschäftsfunktion: Welcher Ablauf wird unterbrochen, wenn es ausfällt: Zugriff, Kauf, Abrechnung, Import oder eine Hilfsaufgabe?
- Exposition: Ob es Daten von Nutzern, Anbietern, Webhooks, Dateien oder internen Netzwerken empfängt.
- Kopplung: Ob seine Typen, Ausnahmen, serialisierten Strukturen oder Abfragen über die Anwendung verteilt vorkommen.
- Abdeckung: Welche Tests das aktuelle Verhalten beschreiben und welche Bereiche nur manuell validiert werden.
- Einschränkungen: PHP-Versionen, Erweiterungen, Datenbank, Queues, externe APIs und regulatorische Anforderungen.
Statische Suchen helfen, Referenzen zu finden, ersetzen jedoch nicht die Beobachtung des Systems. Prüfen Sie asynchrone Jobs, Konsolenskripte, selten genutzte Routen, per Konfiguration aktivierte Integrationen und dynamisch geladenen Code. Eine scheinbar nebensächliche Abhängigkeit kann bei einem Monatsabschluss oder während einer operativen Wiederherstellung entscheidend sein.
Zwischen Aktualisieren, Kapseln, Ersetzen und Entfernen wählen
Es gibt vier zentrale Entscheidungen, und sie schließen sich während einer Migration nicht gegenseitig aus.
- Aktualisieren: Dies ist angebracht, wenn es eine gepflegte Version gibt, deren Schnittstelle und Anforderungen übernommen werden können. Prüfen Sie inkompatible Änderungen, transitive Abhängigkeiten und den erforderlichen Sprung der PHP-Version.
- Kapseln: Es schafft eine eigene Grenze um das aktuelle Paket. Dies ist geeignet, wenn die Kopplung vor der Entscheidung über den Ersatz verringert werden muss oder wenn die Alternative noch nicht ausgereift ist.
- Ersetzen: Es tauscht die Komponente gegen ein anderes Paket, einen externen Dienst oder eine interne Implementierung aus, die auf den erforderlichen Anwendungsfall begrenzt ist. Es muss auf einem expliziten Vertrag beruhen, nicht auf der Ähnlichkeit von Methodennamen.
- Entfernen: Es beseitigt eine Fähigkeit, die keinen Wert mehr bietet, dupliziert wurde oder sich mit nativen Funktionen lösen lässt. Dies ist häufig die Option mit der geringsten künftigen Belastung, erfordert jedoch die Bestätigung, dass keine verborgenen aufrufenden Komponenten existieren.
Vermeiden Sie es, eine Bibliothek nur zu übernehmen, weil sie beliebt oder kompatibel erscheint. Vergleichen Sie Lizenz, beobachtbare Wartung, API-Oberfläche, Fehlermodell, Leistung, Formatunterstützung, Sicherheitsstrategie und Anbieterabhängigkeit. Wenn der Bedarf gering ist, kann eine einfache interne Abstraktion stabiler sein, als ein weiteres umfangreiches Paket einzubinden.
Kompatibilität mit Verträgen und Tests prüfen
Die Dokumentation erklärt die Absicht einer API; der Code in Produktion offenbart den Vertrag, der tatsächlich zählt. Erstellen Sie vor dem Austausch eines Pakets Charakterisierungstests für die aktuellen Fälle. Sie sollen nicht beweisen, dass das alte Design ideal ist, sondern relevante Ergebnisse festhalten, um unerwünschte Änderungen zu erkennen.
Definieren Sie Beispiele für Eingabe und Ausgabe, einschließlich Grenzdaten, Nullwerten, Kodierungen, Datumsangaben, Dezimalgenauigkeit und Fehlermeldungen, die andere Komponenten verarbeiten. Wenn das Paket Dokumente, Ereignisse oder API-Antworten erzeugt, bewahren Sie repräsentative Beispiele auf und validieren Sie deren Struktur.
Aspekte, die häufig unbemerkt brechen
- Persistenz: Unterschiede zwischen fehlenden und Nullwerten, Transaktionen, generierten Identifikatoren und Reihenfolge der Operationen.
- Serialisierung: Feldnamen, Zeitzonen, Datumsformate, Unicode, numerische Typen und Abwärtskompatibilität.
- Integrationen: Authentifizierung, Wiederholungsversuche, Zeitlimits, Signaturen, Paginierung und Interpretation partieller Antworten.
- Fehler: Ausnahmen, Codes, protokollierbare Meldungen und Bedingungen, die einen Wiederholungsversuch oder menschliches Eingreifen auslösen müssen.
- Leistung: Speicherverbrauch, Anzahl der Abfragen, Batch-Größe und Latenz auf kritischen Routen.
Unit-Tests sind für die eigene Logik nützlich, reichen aber nicht aus, wenn sich eine Integration ändert. Ergänzen Sie Integrationstests gegen eine Datenbank oder eine kontrollierte Umgebung sowie Vertragstests an den Grenzen zu externen Systemen. Führen Sie bei Prozessen mit hoher Auswirkung Vergleiche mit anonymisierten oder synthetischen Daten durch, bevor Sie die Änderung für Nutzer aktivieren.
Vor dem Ersatz eine Adapter-Schicht entwerfen
Eine Adapter-Schicht übersetzt den Vertrag der Anwendung in den Vertrag der Abhängigkeit. Statt zuzulassen, dass Controller, Dienste und Queue-Jobs eine Bibliothek direkt aufrufen, definieren Sie eine Schnittstelle, die auf den Geschäftsbedarf ausgerichtet ist. Beispielsweise sollte ein Dienst zur Dokumentkonvertierung eigene Operationen bereitstellen und Domänenobjekte zurückgeben, nicht interne Typen des Pakets.
interface DocumentRenderer
{
public function render(Invoice $invoice): RenderedDocument;
}Die aktuelle Implementierung befindet sich hinter dieser Schnittstelle. Anschließend wird eine zweite Implementierung mit der neuen Komponente hinzugefügt. Das begrenzt die Änderung auf einen Punkt, erleichtert vergleichende Tests und verhindert, dass sich die Besonderheiten des Ersatzes im Code ausbreiten.
Die Abstraktion muss bewusst klein sein. Eine Schnittstelle, die jede Methode der Bibliothek nachbildet, verringert die Kopplung nicht; sie fügt lediglich eine Schicht hinzu. Modellieren Sie die Operationen, die die Anwendung heute benötigt, und dokumentieren Sie relevante Entscheidungen: Was bei einer ungültigen Eingabe geschieht, welche Daten erhalten bleiben und welche Größen- oder Zeitgrenzen gelten.
Eine inkrementelle und reversible Migration durchführen
- Umfang abgrenzen: Wählen Sie einen Ablauf, eine aufrufende Komponente oder eine Operation aus, bevor Sie alle Verwendungen anfassen.
- Verhalten charakterisieren: Ergänzen Sie Tests und Beispiele, die normale Fälle, Grenzfälle und Fehler darstellen.
- Adapter einführen: Behalten Sie die bestehende Implementierung zunächst hinter der neuen Grenze bei.
- Alternative implementieren: Übersetzen Sie Daten und Fehler, ohne den vereinbarten Vertrag zu verändern.
- Ergebnisse vergleichen: Verarbeiten Sie, wenn es sicher ist, gleichwertige Eingaben mit beiden Implementierungen und protokollieren Sie signifikante Unterschiede.
- Aufrufende Komponenten migrieren: Ändern Sie jeweils einen Ablauf, bis direkte Referenzen auf das vorherige Paket entfernt sind.
- Übergangscode entfernen: Entfernen Sie die alte Implementierung, Flags und Kompatibilitätspfade, wenn sie nicht mehr erforderlich sind.
Wenn Sie eine schrittweise Aktivierung verwenden, definieren Sie, welche Metrik das Fortschreiten bestimmt und welche zur Rücknahme zwingt. Ein Konfigurations-Flag kann die Implementierung auswählen, darf jedoch nicht zwei permanente Wahrheitsquellen schaffen. Vermeiden Sie bei Operationen mit Schreibvorgängen, dass beide Pfade dieselbe Ressource ändern, es sei denn, Idempotenz und Abgleich wurden ausdrücklich entworfen.
Mit klaren Diagnosesignalen bereitstellen
Ein Deployment ist nicht gleichbedeutend mit einem vollständigen Release: Code bereitzustellen ist etwas anderes, als sein Verhalten für alle Nutzer zu aktivieren. Trennen Sie beide Zeitpunkte, wenn das Risiko dies rechtfertigt. Stellen Sie die neue Implementierung inaktiv bereit, überprüfen Sie den technischen Zustand und aktivieren Sie die Änderung begrenzt, wenn die Architektur dies zulässt.
Vereinbaren Sie vor Beginn beobachtbare Indikatoren: Fehlerrate pro Operation, Antwortzeiten, Wiederholungsversuche, fehlgeschlagene Jobs, Unterschiede in der Ausgabe und Umfang der Supportvorfälle. Protokollieren Sie eine Implementierungskennung in Traces und Logs, um ein Problem dem alten oder neuen Pfad zuzuordnen, ohne sensible Daten einzuschließen.
Der Rollback muss getestet sein und mit den während des Übergangs erzeugten Daten kompatibel sein. Die Rückkehr zu vorherigem Code löst nicht allein eine irreversible Schemaänderung, ein veröffentlichtes Ereignis oder ein versendetes Dokument. Entwerfen Sie für diese Fälle zuerst eine Kompensation, eine additive Migration oder ein Kompatibilitätsfenster.
Checkliste für eine kritische Abhängigkeit

- Ist die tatsächliche Nutzung und geschäftliche Kritikalität dokumentiert?
- Sind die transitiven Abhängigkeiten und Plattformbeschränkungen bekannt?
- Gibt es einen eigenen Vertrag, der verhindert, dass Pakettypen offengelegt werden?
- Gibt es relevante Charakterisierungs-, Integrations- und Fehlertests?
- Wurden Daten, Serialisierung, Persistenz, Sicherheit und Leistung validiert?
- Kann die Aktivierung begrenzt werden, und berücksichtigt der Rollback Datenänderungen?
- Gibt es ein explizites Datum und Kriterium, um Kompatibilität und temporären Code zu entfernen?
Ein sicherer Ersatz besteht nicht darin, dass das neue Paket kompiliert. Er besteht darin, die wichtigen Ergebnisse zu bewahren, Unterschiede sichtbar zu machen und die Abhängigkeit von Komponenten, die sich nicht mehr mit der Anwendung weiterentwickeln können, dauerhaft zu verringern.



