Eine scheinbar geringfügige Änderung an einer gemeinsamen PHP-Bibliothek kann unabhängige Auslieferungen stoppen. Das Umbenennen eines Parameters, Ändern eines Standardwerts oder Ersetzen einer Exception kann einen Consumer beeinträchtigen, der heute nicht bereitgestellt wird, in einem anderen Repository liegt oder die Komponente indirekt aufruft. Der Fehler kann zur Laufzeit, in einer asynchronen Aufgabe oder beim Deserialisieren von Daten auftreten, die vor der Änderung erzeugt wurden.
Abwärtskompatibilität in PHP bedeutet nicht, jede historische Schnittstelle beizubehalten. Sie ist eine Disziplin, die es Produzenten und Consumern ermöglicht, sich in unterschiedlichem Tempo weiterzuentwickeln, mit einem expliziten Migrationsfenster und einer überprüfbaren Entfernung. Ziel ist es, sowohl erzwungene koordinierte Bereitstellungen als auch die dauerhafte Ansammlung veralteter APIs zu vermeiden.
Ermitteln, was Teil des internen Vertrags ist

Ein interner Vertrag ist jedes Verhalten, von dem ein anderes Modul abhängt, auch wenn es nicht als externe API veröffentlicht ist. Composer-Abhängigkeiten und PHP-Schnittstellen sind ein sichtbarer Teil, decken den Umfang aber nicht vollständig ab. Prüfen Sie vor Änderungen an gemeinsamem Code mindestens diese Elemente:
- Öffentliche Signaturen: Methodennamen, Parameter, Reihenfolge, Typen, Nullbarkeit, Standardwerte und Rückgabetyp.
- Semantik: Was jedes Argument bedeutet, welche Felder erforderlich sind und welches Ergebnis bei einer bestimmten Bedingung erwartet wird.
- Fehler: ausgelöste Exceptions, Fehlercodes, von Clients verarbeitete Meldungen sowie Null- oder leere Ergebnisse.
- Daten: Array-Schlüssel, JSON-Strukturen, Queue-Nachrichten, Domain-Events, serialisierte Dateien und persistierte Daten.
- Nebeneffekte: Senden von Events, Schreiben in die Datenbank, Cache-Invalidierung, HTTP-Aufrufe und Ausführungsreihenfolge.
- Operatives Verhalten: Wiederholungsversuche, Idempotenz, Zeitlimits und Umgang mit vorübergehenden Fehlern.
Beispielsweise ist das Hinzufügen eines Felds zu einer JSON-Antwort in der Regel additiv, ist es aber nicht mehr, wenn ein Consumer eine geschlossene Liste von Eigenschaften validiert. Ebenso kann eine spezifischere Exception technisch korrekt, aber inkompatibel sein, wenn der Consumer die vorherige Exception abfängt, um eine Wiederherstellung auszulösen.
Die Änderung vor dem Schreiben der Implementierung klassifizieren
Die Klassifizierung verhindert, dass eine Designentscheidung zu einem Produktionsvorfall wird. Es empfiehlt sich, sie im Änderungsvorschlag zusammen mit den bekannten Consumern und der Ausstiegsstrategie zu dokumentieren.
Additive Änderungen
Sie führen eine neue Fähigkeit ein, ohne den bestehenden Pfad zu verändern: eine neue Methode, einen optionalen Parameter mit neutraler Semantik, ein zusätzliches Event oder eine neue Version einer Nachricht. Sie sind die bevorzugte Option, wenn Consumer getrennt bereitgestellt werden. Der neue Weg muss neben dem bisherigen bestehen können, und das frühere Verhalten muss nachweisbar erhalten bleiben.
Kompatible Änderungen mit Anpassung
Sie ermöglichen es, das vorherige Ergebnis mittels einer Übersetzungsschicht beizubehalten. Beispielsweise kann eine alte Schnittstelle an einen neuen Service delegieren und dabei Argumente und Ergebnisse umwandeln. Die Anpassung ist sinnvoll, wenn sie lokal begrenzt ist, ein Entfernungsdatum hat und keinen fachlichen Unterschied verbirgt, über den der Consumer bewusst entscheiden muss.
Inkompatible oder unsichere Änderungen
Das Entfernen einer Methode, Verschärfen eines Typs, Ändern der Bedeutung eines Status oder Modifizieren eines persistenten Formats ist in der Regel inkompatibel. Auch jede Änderung ohne verlässliches Inventar der Consumer muss als unsicher behandelt werden. In beiden Fällen reicht es nicht, eine neue Paketversion zu veröffentlichen: Es braucht einen Übergang, eine geplante Migration oder eine separate Vertragsversion.
Ein überprüfbares Inventar der Consumer erstellen
Stützen Sie die Entscheidung nicht nur auf Textsuchen. Eine Komponente kann eine andere über einen Dependency-Container, eine Konfiguration, Reflection, Events, Queues oder eine HTTP-Integration erreichen. Das Inventar muss statische Evidenz und repräsentative Ausführung kombinieren.
- Prüfen Sie in Composer deklarierte Abhängigkeiten, Versionsbeschränkungen und Repositories, die das Paket installieren.
- Suchen Sie nach direkten Verwendungen von Klassen, Schnittstellen, Methoden, Events, Konfigurationsschlüsseln und Nachrichtenformaten.
- Untersuchen Sie Factories, Container-Definitionen, Listener, Commands, Cron-Jobs, Worker und Infrastruktur-Adapter.
- Identifizieren Sie kritische Pfade: Abrechnung, Authentifizierung, Bestellungen, Synchronisierung, Benachrichtigungen und Wiederherstellungsprozesse.
- Erfassen Sie für jeden Consumer den Verantwortlichen, die verwendete Version, den Migrationsweg und die Evidenz, dass die Änderung abgeschlossen wurde.
Die Veröffentlichung einer Bibliothek und die Bereitstellung einer Anwendung sind unterschiedliche Aktionen. Das Veröffentlichen einer kompatiblen Version ermöglicht jedem Consumer ein Update, wenn er bereit ist; die gleichzeitige Bereitstellung aller Consumer macht aus einer gewöhnlichen Weiterentwicklung eine fragile organisatorische Abhängigkeit.
Additive Weiterentwicklung und Adapter an der richtigen Grenze anwenden
Wenn eine neue Anforderung das Modell verändert, führen Sie zuerst eine neue Fähigkeit ein und behalten die bisherige vorübergehend bei. Eine Legacy-Schnittstelle kann an die neue Implementierung delegieren, sofern die Umwandlung eindeutig ist. So können Consumer migrieren, ohne ein einziges gemeinsames Zeitfenster koordinieren zu müssen.
interface LegacyPriceCalculator
{
public function calculate(int $amount): int;
}
final class LegacyPriceCalculatorAdapter implements LegacyPriceCalculator
{
public function __construct(private PriceCalculator $calculator) {}
public function calculate(int $amount): int
{
return $this->calculator->calculate(new Money($amount, 'EUR'))->amount();
}
}Der Adapter gehört normalerweise an die Grenze zwischen Verträgen, nicht in den Domain-Kern. Die Domain muss das aktuelle Modell ausdrücken; die Übersetzung alter Argumente, Sentinel-Werte oder historischer Formate muss in einer dedizierten Schicht liegen. Wenn die Domain Bedingungen für jede Client-Generation beibehält, breitet sich die historische Komplexität auf jede künftige Änderung aus.
Erzwingen Sie keinen Adapter, wenn Informationsverlust oder eine neue fachliche Entscheidung vorliegt. Wenn der alte Vertrag nicht die für das neue Verhalten erforderlichen Daten enthält, behalten Sie während des Übergangs beide Verträge bei oder fordern Sie die zusätzlichen Informationen explizit beim Consumer an.
Deprecation in eine gesteuerte Entfernung überführen
Eine als veraltet markierte API ohne Alternative, Frist oder Verantwortlichen ist keine Deprecation: Sie ist nicht nachverfolgte technische Schuld. Eine sinnvolle Entfernung muss ein Signal im Code, Migrationsanweisungen, eine Entfernungsbedingung und, wenn möglich, Beobachtung der Nutzung enthalten.
- Markieren Sie die Legacy-Methode oder -Klasse mit klarer Dokumentation und geben Sie, falls zutreffend, eine kontrollierte Warnung mit
trigger_error(..., E_USER_DEPRECATED)aus. - Geben Sie die genaue Alternative an, einschließlich Unterschieden bei Semantik, Fehlern und Standardwerten.
- Definieren Sie eine überprüfbare Ausstiegsbedingung: alle inventarisierten Repositories migriert, keine beobachteten Aufrufe oder Ende des Supports für eine bestimmte Version.
- Weisen Sie einen Verantwortlichen zu, der den Fortschritt prüft und die Schicht entfernt, sobald die Bedingung erfüllt ist.
Vermeiden Sie wahllose Warnungen auf Pfaden mit hohem Volumen ohne Aggregationsstrategie: Das Rauschen kann relevante Signale verdecken und die Betriebskosten erhöhen. Die Observability muss eine konkrete Frage beantworten: Welche Consumer verwenden noch den vorherigen Vertrag und auf welchem Pfad?
Den Übergang testen und die Auslieferungssequenz ausführen
Unit-Tests der Komponente allein belegen nicht, dass die Consumer weiterhin funktionieren. Fügen Sie Vertragstests für die Eingaben, Ausgaben und Fehler hinzu, die jeder Consumer benötigt. Behalten Sie Regressionstestfälle für die alte Schnittstelle bei, solange sie unterstützt wird, und testen Sie fehlende Werte, frühere serialisierte Nutzdaten und erwartete Exceptions ausdrücklich.
Die sichere Sequenz folgt in der Regel dieser Reihenfolge:
- Den neuen Vertrag oder die additive Implementierung veröffentlichen und den vorherigen Pfad beibehalten.
- Consumer unabhängig aktualisieren und bereitstellen; verwenden Sie Integrationstests, wo das Risiko dies rechtfertigt.
- Fehler, Deprecation-Warnungen und die Nutzung der Legacy-Schnittstelle beobachten.
- Das Migrationsinventar bestätigen und erkannte indirekte Consumer behandeln.
- Den Adapter oder alten Vertrag in einer separaten Auslieferung entfernen, mit Tests, die dessen Abwesenheit bestätigen.
Checkliste zur Genehmigung der Änderung

- Ist der betroffene Vertrag über die PHP-Signatur hinaus definiert?
- Ist die Änderung als additiv, anpassbar, inkompatibel oder unsicher klassifiziert?
- Gibt es ein Inventar der Consumer, einschließlich Events, Daten und indirekter Pfade?
- Vermeidet die Lösung, gleichzeitige Bereitstellungen zu verlangen?
- Liegt der Adapter, falls vorhanden, außerhalb der Domain und ist seine Entfernung vorgesehen?
- Wurden vorheriges Verhalten, neue Fähigkeit und erwartete Fehler getestet?
- Gibt die Deprecation Alternative, Entfernungsbedingung und Verantwortlichen an?
- Gibt es ein Signal, um versteckte Abhängigkeiten vor dem Entfernen der API zu erkennen?
Die richtige Entscheidung besteht weder darin, Kompatibilität unbegrenzt beizubehalten, noch darin, vollständige Koordination vorzuschreiben. Sie besteht darin, einen Übergang mit Grenzen zu gestalten: Notwendiges bewahren, anhand von Evidenz migrieren und historische Kompatibilität entfernen, wenn sie keine Sicherheit mehr bietet.



