> ## Documentation Index
> Fetch the complete documentation index at: https://kawax.biz/llms.txt
> Use this file to discover all available pages before exploring further.

# Service-Dekoration und extend in Paketen

> Lesen Sie Container::extend() in Laravel 13 und lernen Sie vertragserhaltende Decorators, Registrierung vor und nach der Auflösung, die Lebensdauer von Singleton-Dekorationen, doppelte Dekoration und Tests bei Updates kennen.

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](https://kawax.biz/de/advanced/package-service-resolution.md) 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

| API | Zweck | Rückgabewert des Callbacks |
| - | - | - |
| `bind()`, `singleton()` | Erzeugungsweise und Teilung eines Service registrieren | Der zu erzeugende Service |
| `afterResolving()` | Konfiguration u. Ä. auf den aufgelösten Service anwenden | Wird nicht zum Ersetzen des Service verwendet |
| `extend()` | Erzeugungslogik beibehalten und den Service dekorieren oder ändern | Muss immer den geänderten Service zurückgeben |

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.

> [!NOTE]
>
> 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/`.

```php theme={null}
<?php

namespace Acme\Messages;

interface MessageFormatter
{
    public function format(string $message): string;
}
```

Die undekorierte Implementierung gibt die Eingabe unverändert zurück.

```php theme={null}
<?php

namespace Acme\Messages;

final class PlainMessageFormatter implements MessageFormatter
{
    public function format(string $message): string
    {
        return $message;
    }
}
```

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.

```php theme={null}
<?php

namespace Acme\Messages;

final class PrefixedMessageFormatter implements MessageFormatter
{
    public function __construct(
        private readonly MessageFormatter $inner,
    ) {}

    public function format(string $message): string
    {
        return '[Acme] '.$this->inner->format($message);
    }
}
```

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](https://kawax.biz/de/advanced/package-discovery.md).

```php theme={null}
<?php

namespace Acme\Messages;

use Illuminate\Support\ServiceProvider;

final class PrefixServiceProvider extends ServiceProvider
{
    public function register(): void
    {
        $this->app->extend(
            MessageFormatter::class,
            static fn (MessageFormatter $formatter): MessageFormatter =>
                new PrefixedMessageFormatter($formatter),
        );
    }
}
```

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()`.

```php theme={null}
use Acme\Messages\MessageFormatter;
use Acme\Messages\PlainMessageFormatter;

$this->app->bind(MessageFormatter::class, PlainMessageFormatter::class);
```

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.

> [!WARNING]
>
> 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.

```mermaid theme={null}
flowchart LR
    A["Ursprüngliche Erzeugung"] --> B["Service"]
    B --> C["extender A<br>Mit Wrapper A einhüllen"]
    C --> D["extender B<br>Mit Wrapper B einhüllen"]
    D --> E["Speichern, falls geteilt"]
    E --> F["Hooks nach der Auflösung und Nutzer"]
```

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.

| Zustand bei der Registrierung | Verhalten von `extend()` | Erhalten für künftige Neuerzeugungen |
| - | - | - |
| Noch nicht aufgelöst, keine gespeicherte Instanz | Fügt den Extender dem Array hinzu | Wird bei späteren normalen Erzeugungen angewendet |
| Ein normales `bind()` wurde früher aufgelöst, aber keine Instanz ist gespeichert | Fügt den Extender hinzu und versucht eine Rebound-Benachrichtigung | Wird bei späteren normalen Erzeugungen angewendet |
| Ein Singleton- oder `instance()`-Objekt ist gespeichert | Dekoriert sofort, ersetzt den gespeicherten Wert durch den Rückgabewert und sendet eine Rebound-Benachrichtigung | **Dieser Extender wird nicht dem Array hinzugefügt** |

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](https://kawax.biz/de/advanced/package-service-lifetimes.md).

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](https://kawax.biz/de/advanced/package-testing.md).

```php theme={null}
<?php

namespace Acme\Messages\Tests;

use Acme\Messages\MessageFormatter;
use Acme\Messages\PlainMessageFormatter;
use Acme\Messages\PrefixedMessageFormatter;
use Acme\Messages\PrefixServiceProvider;
use Illuminate\Container\Container;
use PHPUnit\Framework\TestCase;

final class ServiceDecorationTest extends TestCase
{
    public function test_extension_is_lazy_and_can_precede_binding(): void
    {
        $container = new Container;
        $builds = 0;

        (new PrefixServiceProvider($container))->register();
        $container->bind(MessageFormatter::class, function () use (&$builds) {
            $builds++;

            return new PlainMessageFormatter;
        });

        $this->assertSame(0, $builds);

        $first = $container->make(MessageFormatter::class);
        $second = $container->make(MessageFormatter::class);

        $this->assertSame('[Acme] ready', $first->format('ready'));
        $this->assertSame('[Acme] ready', $second->format('ready'));
        $this->assertNotSame($first, $second);
        $this->assertSame(2, $builds);
    }

    public function test_early_extension_survives_singleton_recreation(): void
    {
        $container = new Container;
        $container->singleton(MessageFormatter::class, PlainMessageFormatter::class);
        (new PrefixServiceProvider($container))->register();

        $first = $container->make(MessageFormatter::class);

        $this->assertInstanceOf(PrefixedMessageFormatter::class, $first);
        $this->assertSame($first, $container->make(MessageFormatter::class));

        $container->forgetInstance(MessageFormatter::class);
        $second = $container->make(MessageFormatter::class);

        $this->assertNotSame($first, $second);
        $this->assertSame('[Acme] ready', $second->format('ready'));
    }

    public function test_late_singleton_extension_is_not_retained_as_a_callback(): void
    {
        $container = new Container;
        $container->singleton(MessageFormatter::class, PlainMessageFormatter::class);
        $original = $container->make(MessageFormatter::class);

        (new PrefixServiceProvider($container))->register();
        $decorated = $container->make(MessageFormatter::class);

        $this->assertInstanceOf(PrefixedMessageFormatter::class, $decorated);
        $this->assertNotSame($original, $decorated);
        $this->assertSame('ready', $original->format('ready'));
        $this->assertSame('[Acme] ready', $decorated->format('ready'));

        $container->forgetInstance(MessageFormatter::class);
        $recreated = $container->make(MessageFormatter::class);

        $this->assertInstanceOf(PlainMessageFormatter::class, $recreated);
        $this->assertSame('ready', $recreated->format('ready'));
    }
}
```

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

> [card] [Hooks nach der Service-Auflösung in Paketen](https://kawax.biz/de/advanced/package-service-resolution.md)
>
> Behandelt callAfterResolving(), das Objekte konfiguriert, ohne sie zu ersetzen, und worauf bei der Anzahl der Ausführungen zu achten ist.

> [card] [Lebenszyklus-Design für Paket-Services](https://kawax.biz/de/advanced/package-service-lifetimes.md)
>
> Behandelt bind, singleton und scoped sowie die Abhängigkeiten, die erzeugte Services festhalten.

## Herangezogene Primärquellen

* [Offizielle Laravel-Dokumentation: Extending bindings und Rebinding](https://github.com/laravel/docs/blob/13.x/container.md#extending-bindings)
* [Offizielle Laravel-Dokumentation: Service Providers](https://github.com/laravel/docs/blob/13.x/providers.md)
* [Offizielle Laravel-Dokumentation: Paketentwicklung und automatische Erkennung](https://github.com/laravel/docs/blob/13.x/packages.md)
* [Container: extend(), resolve(), rebound(), instance(), forgetExtenders()](https://github.com/laravel/framework/blob/v13.40.0/src/Illuminate/Container/Container.php)
* [Offizielle Tests: verzögerte Erzeugung, Registrierung vor dem Binden, Aliase, mehrere Extender](https://github.com/laravel/framework/blob/v13.40.0/tests/Container/ContainerExtendTest.php)
* [ServiceProvider: Konstruktor und register()](https://github.com/laravel/framework/blob/v13.40.0/src/Illuminate/Support/ServiceProvider.php)


## Related topics

- [Hooks nach der Service-Auflösung in Paketen](https://kawax.biz/de/advanced/package-service-resolution.md)
- [Weiterführende Themen](https://kawax.biz/de/advanced/index.md)
- [String-Validierungsregeln in Paketen registrieren](https://kawax.biz/de/advanced/package-validation-extensions.md)
- [Log-Channels in Paketen erweitern und neu auflösen](https://kawax.biz/de/advanced/package-logging.md)
- [Storage-Treiber in Paketen registrieren und neu auflösen](https://kawax.biz/de/advanced/package-filesystem-drivers.md)


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.