Zum Hauptinhalt springen
Manchmal soll ein Paket Verhalten ergänzen und dabei die Service-Implementierung beibehalten, die die nutzende Anwendung gewählt hat. Mit extend() des Containers können Sie den aufgelösten Service in einen Decorator einhüllen, ohne die ursprüngliche Erzeugungslogik neu zu definieren. Diese Seite bezieht sich auf den Standard-Branch 13.x der offiziellen Laravel-Dokumentation sowie auf die offizielle Implementierung und die Tests des neuesten Framework-Releases v13.40.0, geprüft am 9. November 2026. Neben dem Unterschied zu den Hooks nach der Service-Auflösung behandelt sie, wie lange eine Dekoration erhalten bleibt. Das hängt davon ab, ob beim Registrieren bereits eine Instanz gespeichert ist.

Konfiguration ergänzen und Objekt ersetzen trennen

Ein extend()-Callback erhält den Ziel-Service und den Container. Sie können dasselbe Objekt konfigurieren und zurückgeben oder einen neuen Decorator zurückgeben. Wenn Sie das return vergessen, wird null an die weitere Auflösung übergeben.
Hier geht es um Illuminate\Container\Container::extend(). Das ist eine andere API als das extend() von Managern wie Cache oder Mail, das einen Treibernamen und seine Erzeugungslogik registriert.

Einen Decorator mit demselben Vertrag erstellen

Die ursprüngliche Implementierung einhüllen statt ersetzen

Zur Veranschaulichung legen Sie den folgenden Vertrag zum Formatieren von Nachrichten in src/MessageFormatter.php ab. Auch die folgenden Klassen liegen jeweils in einer gleichnamigen PHP-Datei unter src/.
Die undekorierte Implementierung gibt die Eingabe unverändert zurück.
Der Decorator implementiert dieselbe Schnittstelle und delegiert an den übergebenen Service. In diesem Beispiel stellt er dem Rückgabewert des Delegaten ein paketspezifisches Präfix voran.
Wenn Nutzer die konkrete Klasse PlainMessageFormatter als Typ angeben, kann ein Decorator, der nur dieselbe Schnittstelle implementiert, sie nicht ersetzen. Machen Sie MessageFormatter zur öffentlichen Abhängigkeit und halten Sie den Vertrag für Argumente, Rückgabewerte, Exceptions usw. ein. Dynamisches Weiterleiten von Methoden allein ersetzt keine Implementierung einer PHP-Schnittstelle.

Die Dekoration im Provider registrieren

src/PrefixServiceProvider.php, das die Dekoration bereitstellt, überschreibt die Erzeugungsweise nicht, sondern registriert nur eine Erweiterung im Container. Zum Laden des Providers siehe Interne Struktur der automatischen Paket-Erkennung.
Als zweites Argument erhält der Callback auch den Container, aber dieses Beispiel benötigt ihn nicht, daher wird er nicht deklariert. Verwenden Sie das Argument $formatter als Ziel-Service. Ein erneutes make() desselben Schlüssels im Callback kann eine zirkuläre Auflösung verursachen. Binden Sie die ursprüngliche Implementierung in der nutzenden Anwendung, zum Beispiel in AppServiceProvider::register().
Sie können extend() auch vor dem bind() des Ziels registrieren. Ist das Ziel noch nicht aufgelöst und keine Instanz gespeichert, erzeugt die Registrierung allein den ursprünglichen Service nicht. Auch die offiziellen Tests prüfen die verzögerte Erzeugung und die Registrierung vor dem Binden. Danach gibt app(MessageFormatter::class)->format('Versendet.') den Wert [Acme] Versendet. zurück. Auch wenn die ursprüngliche Implementierung durch eine andere MessageFormatter-Implementierung ersetzt wird, hüllt der Decorator diese ein.
Registrieren Sie grundsätzlich vor der ersten Auflösung. Ein anderer Provider könnte den Service zuerst verwenden, daher verhindert ein bloßes Verschieben nach boot() keine Auflösung vor der Registrierung. Wenn Sie außerdem einen Provider, der nur Erweiterungen registriert, leichtfertig verzögert laden, kann der Auslöser für die Registrierung selbst entfallen.

Ziel und Reihenfolge der Dekoration verstehen

Bei einer normalen Auflösung erzeugt der Container den ursprünglichen Service, ersetzt ihn in Registrierungsreihenfolge durch den Rückgabewert jedes Extenders, speichert ihn als geteilte Instanz und ruft dann die Hooks nach der Auflösung auf. Registrieren Sie A und dann B, ist das Ergebnis B(A(ursprünglicher Service)). Methodenaufrufe der Nutzer beginnen beim äußeren B, verwechseln Sie also nicht Registrierungsreihenfolge und Startreihenfolge der Verarbeitung. Wenn Sie von der Dekorationsreihenfolge mehrerer Pakete abhängen, legen Sie die Reihenfolge in der nutzenden Anwendung explizit fest und testen Sie die Kombination. Extender gelten für den Auflösungsschlüssel, nachdem der Container Aliase normalisiert hat. Eine Registrierung auf MessageFormatter::class wird nicht schon deshalb angewendet, weil die Typen übereinstimmen, wenn PlainMessageFormatter::class direkt aufgelöst wird. Verwenden Sie einheitlich die Schnittstelle.

Dekorationen gespeicherter Instanzen bleiben anders erhalten

Container::extend() in v13.40.0 verzweigt danach, ob eine Instanz des Ziels im Container gespeichert ist. Die letzte Zeile ist wichtig. Wenn Sie ein aufgelöstes Singleton dekorieren und danach forgetInstance() aufrufen oder denselben Schlüssel neu binden, wird eine nur sofort angewendete Dekoration bei der nächsten Erzeugung nicht erneut angewendet. Das unterscheidet sich von einer Registrierung im Extender-Array vor der ersten Auflösung. Auch scoped() verwendet gespeicherte Instanzen. Gehen Sie also nicht davon aus, dass eine mitten im Scope spät registrierte Dekoration für den nächsten Scope registriert bleibt. Prüfen Sie dies zusammen mit dem Lebenszyklus-Design für Paket-Services. Wenn Sie außerdem später ein Objekt direkt mit instance() speichern, durchläuft es nicht die Schleife, die Extender bei normalen Erzeugungen anwendet. Auch bei Ersetzungen in Tests gilt nicht immer: „Es ist registriert, also wird das übergebene Objekt automatisch dekoriert.“

Bereits verteilte Referenzen werden nicht automatisch ersetzt

Wenn Sie wie in diesem Beispiel einen neuen Wrapper zurückgeben, ändert sich das ursprüngliche Objekt nicht, das bereits in einer Eigenschaft eines anderen Service gespeichert ist, auch wenn sich der gespeicherte Wert im Container ändert. Eine rebinding()-Benachrichtigung übergibt den neuen Service an registrierte Callbacks. Sie sucht nicht alle Abhängigen und schreibt sie um. Auch das Registrieren eines Benachrichtigungs-Callbacks mit rebinding() ist nicht immer verzögert. Ist das Ziel bereits gebunden, wird auch bei der Registrierung das Ergebnis von make() zurückgegeben. Auf dem Weg, bei dem Sie ein früher aufgelöstes normales Binding mit extend() erweitern, kann für die Benachrichtigung ebenfalls make() aufgerufen werden, wenn ein Rebound-Callback existiert. Gehen Sie nicht pauschal davon aus, dass Initialisierung und Nebeneffekte „bei der nächsten Verwendung“ stattfinden.

Doppelte Dekoration und übernommenen Zustand vermeiden

extend() entfernt Registrierungen derselben Closure oder desselben Decorators nicht automatisch als Duplikate. Wird register() dieses Providers zweimal auf denselben Container angewendet, kann auch das Präfix doppelt erscheinen.
  • Bündeln Sie die Registrierung von Dekorationen im festen Boot-Pfad des Providers und wiederholen Sie sie nicht pro Request oder Job.
  • Verhindern Sie Duplikate nicht mit einem einzelnen statischen Flag, das alle Container teilen. Tests und neue Anwendungen würden benötigte Registrierungen verpassen.
  • Die Dekoration eines Singletons bleibt für dieselbe geteilte Lebensdauer erhalten. Binden Sie nicht den aktuellen Benutzer, Request o. Ä. fest an den Wrapper.
  • forgetExtenders() löscht nur das Registrierungs-Array. Bereits erzeugte Wrapper werden dadurch nicht zurückverwandelt.
Dieses Beispiel ändert den Ausgabestring. Präfix, Anzahl der delegierten Aufrufe und Exception-Behandlung werden ebenfalls zum öffentlichen Verhalten des Pakets. Behandeln Sie sie daher nicht als bloße „interne Implementierungsänderung“, sondern prüfen Sie die Kompatibilität.

Registrierungszeitpunkte mit demselben Container testen

Das folgende PHPUnit-Beispiel setzt voraus, dass die obigen Klassen über den Autoload des Pakets geladen werden können. Es wendet register() des Providers direkt auf einen Container an und prüft, wie lange die Dekoration erhalten bleibt, ohne HTTP oder externe Dienste. Prüfen Sie die automatische Erkennung und den Start der gesamten Anwendung separat mit Laravel-Pakete mit Orchestra Testbench testen.
Der letzte Test empfiehlt keine späte Registrierung, sondern macht die Grenze in der geprüften Version explizit. Gestalten Sie forgetInstance() allein nicht als API zum Entfernen oder Neuregistrieren von Dekorationen. Prüfen Sie vor einem Release auch die Reihenfolge mehrerer Extender, doppelte Registrierungen, Ersetzungen per instance() und Referenzen, die Nutzer bereits halten. Wenn Sie weitere Laravel-Versionen unterstützen, lesen Sie neben Auflösungsschlüsseln und Typverträgen auch die Verzweigungen in extend() und die offiziellen Tests erneut.

Verwandte Seiten

Hooks nach der Service-Auflösung in Paketen

Behandelt callAfterResolving(), das Objekte konfiguriert, ohne sie zu ersetzen, und worauf bei der Anzahl der Ausführungen zu achten ist.

Lebenszyklus-Design für Paket-Services

Behandelt bind, singleton und scoped sowie die Abhängigkeiten, die erzeugte Services festhalten.

Herangezogene Primärquellen

Zuletzt geändert am 9. November 2026