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

# Servicedecoratie en extend in packages

> Lees Container::extend() van Laravel 13 en leer decorators die het contract behouden, registratie voor en na het resolven, hoe lang singletondecoraties behouden blijven, dubbele decoratie en testen bij updates.

Soms wil een package gedrag toevoegen en tegelijk de service-implementatie behouden die de gebruikende applicatie heeft gekozen. Met `extend()` van de container kun je de geresolvede service in een decorator verpakken zonder de oorspronkelijke aanmaaklogica opnieuw te definiëren.

Deze pagina verwijst naar de standaardbranch `13.x` van de officiële Laravel-documentatie en naar de officiële implementatie en tests van de nieuwste Framework-release `v13.40.0`, gecontroleerd op 9 november 2026. Naast het verschil met [hooks na het resolven van packageservices](https://kawax.biz/nl/advanced/package-service-resolution.md) behandelt ze hoe lang een decoratie behouden blijft. Dat hangt af van **of er bij het registreren al een instantie is opgeslagen**.

## Configuratie toevoegen en het object vervangen scheiden

| API | Doel | Returnwaarde van de callback |
| - | - | - |
| `bind()`, `singleton()` | Registreren hoe een service wordt aangemaakt en of hij gedeeld wordt | De aan te maken service |
| `afterResolving()` | Configuratie e.d. toepassen op de geresolvede service | Wordt niet gebruikt om de service te vervangen |
| `extend()` | De aanmaaklogica behouden en de service decoreren of wijzigen | Moet altijd de gewijzigde service retourneren |

Een `extend()`-callback ontvangt de doelservice en de container. Je kunt hetzelfde object configureren en retourneren, of een nieuwe decorator retourneren. Vergeet je de `return`, dan wordt `null` doorgegeven aan de rest van het resolveproces.

> [!NOTE]
>
> Het gaat hier om `Illuminate\Container\Container::extend()`. Dat is een andere API dan de `extend()` van managers zoals Cache en Mail, die een drivernaam en de aanmaaklogica registreert.

## Een decorator maken die hetzelfde contract implementeert

### De oorspronkelijke implementatie verpakken in plaats van vervangen

Ter illustratie plaats je het volgende contract voor het opmaken van berichten in `src/MessageFormatter.php`. Ook de onderstaande klassen staan elk in een PHP-bestand met dezelfde naam in `src/`.

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

namespace Acme\Messages;

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

De ongedecoreerde implementatie retourneert de invoer ongewijzigd.

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

namespace Acme\Messages;

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

De decorator implementeert dezelfde interface en delegeert naar de ontvangen service. In dit voorbeeld voegt hij een packagespecifiek voorvoegsel toe aan de returnwaarde van de gedelegeerde.

```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);
    }
}
```

Als gebruikers de concrete klasse `PlainMessageFormatter` als type opgeven, kan een decorator die alleen dezelfde interface implementeert die niet vervangen. Maak `MessageFormatter` de afhankelijkheid die je publiceert en behoud het contract voor argumenten, returnwaarden, excepties enzovoort. Alleen dynamisch methoden doorsturen is geen vervanging voor het implementeren van een PHP-interface.

### De decoratie registreren in een provider

`src/PrefixServiceProvider.php`, dat de decoratie levert, overschrijft niet hoe de service wordt aangemaakt. Het registreert alleen een extensie in de container. Zie voor het laden van de provider [De interne structuur van package discovery](https://kawax.biz/nl/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),
        );
    }
}
```

De callback ontvangt als tweede argument ook de container, maar dit voorbeeld heeft die niet nodig en declareert hem daarom niet. Gebruik het argument `$formatter` als doelservice. Dezelfde sleutel opnieuw met `make()` resolven binnen de callback kan een circulaire resolutie veroorzaken.

Bind de oorspronkelijke implementatie in de gebruikende applicatie, bijvoorbeeld in `AppServiceProvider::register()`.

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

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

Je kunt `extend()` ook registreren vóór de `bind()` van het doel. Als het doel nog niet is geresolved en er geen instantie is opgeslagen, maakt de registratie alleen de oorspronkelijke service niet aan. Ook de officiële tests controleren uitgestelde aanmaak en registratie vóór de binding.

Daarna retourneert `app(MessageFormatter::class)->format('Verzonden.')` de waarde `[Acme] Verzonden.`. Ook als de oorspronkelijke implementatie wordt vervangen door een andere `MessageFormatter`-implementatie, verpakt de decorator die implementatie.

> [!WARNING]
>
> Registreer als uitgangspunt vóór de eerste resolutie. Een andere provider kan de service eerder gebruiken, dus de registratie alleen naar `boot()` verplaatsen voorkomt geen resolutie vóór de registratie. Als je bovendien een provider die alleen extensies registreert zomaar uitgesteld maakt, kan de registratie zelf haar startvoorwaarde verliezen.

## Begrijpen waar decoratie wordt toegepast en in welke volgorde

Bij een normale resolutie maakt de container de oorspronkelijke service aan, vervangt hem in registratievolgorde door de returnwaarde van elke extender, slaat hem op als gedeelde instantie en roept daarna de hooks na het resolven aan.

```mermaid theme={null}
flowchart LR
    A["Oorspronkelijke aanmaak"] --> B["Service"]
    B --> C["extender A<br>Verpakken met Wrapper A"]
    C --> D["extender B<br>Verpakken met Wrapper B"]
    D --> E["Opslaan indien gedeeld"]
    E --> F["Hooks na resolven en gebruikers"]
```

Registreer je eerst A en dan B, dan is het resultaat `B(A(oorspronkelijke service))`. Methodeaanroepen van gebruikers beginnen bij de buitenste B, dus verwar de registratievolgorde niet met de volgorde waarin de verwerking start. Ben je afhankelijk van de decoratievolgorde van meerdere packages, leg de volgorde dan expliciet vast in de gebruikende applicatie en test de combinatie.

Extenders worden toegepast op de **resolutiesleutel** nadat de container aliassen heeft genormaliseerd. Een registratie op `MessageFormatter::class` wordt niet toegepast wanneer `PlainMessageFormatter::class` direct wordt geresolved, alleen omdat de types overeenkomen. Gebruik consequent de interface.

## Decoratie van een opgeslagen instantie blijft anders behouden

`Container::extend()` in `v13.40.0` vertakt op basis van of er een instantie van het doel in de container is opgeslagen.

| Toestand bij registratie | Gedrag van `extend()` | Behouden voor toekomstige aanmaak |
| - | - | - |
| Nog niet geresolved en geen opgeslagen instantie | Voegt de extender toe aan de array | Toegepast bij latere normale aanmaak |
| Een normale `bind()` is eerder geresolved, maar er is geen instantie opgeslagen | Voegt de extender toe en probeert een rebound-melding | Toegepast bij latere normale aanmaak |
| Een singleton- of `instance()`-object is opgeslagen | Decoreert het direct, vervangt de opgeslagen waarde door de returnwaarde en stuurt een rebound-melding | **Deze extender wordt niet aan de array toegevoegd** |

De laatste rij is belangrijk. Als je een geresolvede singleton decoreert en daarna `forgetInstance()` aanroept of dezelfde sleutel opnieuw bindt, wordt **een decoratie die alleen ter plekke is toegepast** bij de volgende aanmaak niet opnieuw toegepast. Dat verschilt van registreren in de extender-array vóór de eerste resolutie.

Ook `scoped()` gebruikt opgeslagen instanties, dus ga er niet van uit dat een decoratie die halverwege een scope laat is geregistreerd, voor de volgende scope geregistreerd blijft. Controleer dit samen met [Levensduur van packageservices ontwerpen](https://kawax.biz/nl/advanced/package-service-lifetimes.md).

Als je later een object direct opslaat met `instance()`, gaat het bovendien niet door de lus die extenders toepast bij normale aanmaak. Ook bij vervangingen in tests geldt niet altijd: "het is geregistreerd, dus het object dat ik doorgeef wordt automatisch gedecoreerd".

### Al uitgedeelde referenties worden niet automatisch vervangen

Als je zoals in dit voorbeeld een nieuwe wrapper retourneert, verandert het oorspronkelijke object dat al in een property van een andere service is opgeslagen niet, ook al verandert de opgeslagen waarde in de container. Een `rebinding()`-melding geeft de nieuwe service door aan geregistreerde callbacks. Ze zoekt niet alle afhankelijke objecten op om ze te herschrijven.

Ook het registreren van een meldingscallback met `rebinding()` is niet altijd uitgesteld. Als het doel al gebonden is, wordt ook bij de registratie het resultaat van `make()` geretourneerd. Op het pad waarbij je `extend()` toepast op een eerder geresolvede normale binding, kan voor de melding ook `make()` worden aangeroepen als er een rebound-callback bestaat. Ga er niet standaard van uit dat initialisatie en bijwerkingen "bij het volgende gebruik" plaatsvinden.

## Dubbele decoratie en meegenomen state vermijden

`extend()` ontdubbelt registraties van dezelfde closure of dezelfde decorator niet automatisch. Als `register()` van deze provider twee keer op dezelfde container wordt toegepast, kan het voorvoegsel ook dubbel worden toegevoegd.

* Bundel de registratie van decoraties in het vaste opstartpad van de provider en herhaal die niet per request of job.
* Voorkom duplicaten niet met één statische vlag die door alle containers wordt gedeeld. Tests en nieuwe applicaties missen dan registraties die ze nodig hebben.
* De decoratie van een singleton blijft behouden voor dezelfde gedeelde levensduur. Leg de huidige gebruiker, request of vergelijkbare state niet vast in de wrapper.
* `forgetExtenders()` wist alleen de registratie-array. Het zet al aangemaakte wrappers niet terug naar het origineel.

Dit voorbeeld wijzigt de uitvoerstring. Het voorvoegsel, het aantal gedelegeerde aanroepen en de afhandeling van excepties worden ook publiek gedrag van het package. Behandel ze dus niet als een "interne implementatiewijziging", maar controleer de compatibiliteit.

## Registratiemomenten testen met dezelfde container

Het volgende PHPUnit-voorbeeld gaat ervan uit dat de bovenstaande klassen via de autoload van het package kunnen worden geladen. Het past `register()` van de provider direct toe op een Container en controleert zonder HTTP of externe services hoe lang de decoratie behouden blijft. Controleer package discovery en het opstarten van de hele applicatie apart met [Laravel-packages testen met Orchestra Testbench](https://kawax.biz/nl/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'));
    }
}
```

De laatste test beveelt late registratie niet aan, maar maakt de grens in de gecontroleerde versie expliciet. Ontwerp `forgetInstance()` niet als enige API om decoraties te verwijderen of opnieuw te registreren.

Controleer vóór een release ook de volgorde van meerdere extenders, dubbele registraties, vervanging met `instance()` en referenties die gebruikers al vasthouden. Lees bij het toevoegen van ondersteunde Laravel-versies naast de resolutiesleutels en typecontracten ook de vertakkingen in `extend()` en de officiële tests opnieuw.

## Gerelateerde pagina's

> [card] [Hooks na het resolven van packageservices](https://kawax.biz/nl/advanced/package-service-resolution.md)
>
> Behandelt callAfterResolving(), dat objecten configureert zonder ze te vervangen, en waar je op moet letten bij hoe vaak het wordt toegepast.

> [card] [Levensduur van packageservices ontwerpen](https://kawax.biz/nl/advanced/package-service-lifetimes.md)
>
> Behandelt bind, singleton en scoped en de afhankelijkheden die aangemaakte services vasthouden.

## Geraadpleegde primaire bronnen

* [Officiële Laravel-documentatie: Extending bindings en Rebinding](https://github.com/laravel/docs/blob/13.x/container.md#extending-bindings)
* [Officiële Laravel-documentatie: Service Providers](https://github.com/laravel/docs/blob/13.x/providers.md)
* [Officiële Laravel-documentatie: packageontwikkeling en package discovery](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)
* [Officiële tests: uitgestelde aanmaak, registratie vóór binding, aliassen, meerdere extenders](https://github.com/laravel/framework/blob/v13.40.0/tests/Container/ContainerExtendTest.php)
* [ServiceProvider: constructor en register()](https://github.com/laravel/framework/blob/v13.40.0/src/Illuminate/Support/ServiceProvider.php)


## Related topics

- [Hooks na het resolven van packageservices](https://kawax.biz/nl/advanced/package-service-resolution.md)
- [Geavanceerde onderwerpen](https://kawax.biz/nl/advanced/index.md)
- [Mailtransports voor packages registreren en testen](https://kawax.biz/nl/advanced/package-mail-transports.md)
- [Stringvalidatieregels registreren vanuit packages](https://kawax.biz/nl/advanced/package-validation-extensions.md)
- [Logchannels voor packages uitbreiden en opnieuw resolven](https://kawax.biz/nl/advanced/package-logging.md)


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