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

# Package service decoration and extend

> Read Laravel 13's Container::extend() to learn contract-preserving decorators, registering before and after resolution, how long singleton decorations last, double decoration, and testing across upgrades.

Sometimes a package needs to add behavior while keeping the service implementation the consuming application chose. The container's `extend()` lets you wrap the resolved service in a decorator without redefining the original construction logic.

This page refers to the default branch `13.x` of the official Laravel documentation and to the official implementation and tests of the latest Framework release `v13.40.0`, checked on November 9, 2026. Besides the difference from [service resolution hooks](https://kawax.biz/en/advanced/package-service-resolution.md), it covers how long a decoration is retained, which changes depending on **whether an instance is already stored when you register it**.

## Separate adding configuration from replacing the object

| API | Purpose | Callback return value |
| - | - | - |
| `bind()`, `singleton()` | Register how a service is built and whether it is shared | The service to build |
| `afterResolving()` | Apply configuration and similar changes to the resolved service | Not used to replace the service |
| `extend()` | Keep the construction logic and decorate or modify the service | Must always return the modified service |

An `extend()` callback receives the target service and the container. You can configure the same object and return it, or return a new decorator. If you forget to `return`, `null` is passed to the rest of the resolution process.

> [!NOTE]
>
> This page covers `Illuminate\Container\Container::extend()`. It is a different API from the `extend()` on managers such as Cache and Mail, which registers a driver name and its creation logic.

## Build a decorator that implements the same contract

### Wrap the original implementation without replacing it

For illustration, put the following contract for formatting messages in `src/MessageFormatter.php`. Each of the classes below is likewise assumed to live in a PHP file of the same name in `src/`.

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

namespace Acme\Messages;

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

The undecorated implementation returns its input unchanged.

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

namespace Acme\Messages;

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

The decorator implements the same interface and delegates to the service it receives. In this example, it adds a package-specific prefix to the delegate's return value.

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

If consumers type-hint the concrete `PlainMessageFormatter` class, a decorator that only implements the same interface cannot stand in for it. Make `MessageFormatter` the dependency you expose, and keep the contract for arguments, return values, exceptions and so on. Dynamic method forwarding alone is no substitute for implementing a PHP interface.

### Register the decoration in a provider

`src/PrefixServiceProvider.php`, which provides the decoration, does not override how the service is built. It only registers an extension with the container. For loading the provider, see [Package discovery](https://kawax.biz/en/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),
        );
    }
}
```

The container is also passed as the callback's second argument, but this example does not need it, so it is not declared. Use the `$formatter` argument as the target service. Calling `make()` on the same key again inside the callback can cause circular resolution.

Bind the original implementation in the consuming application, for example in `AppServiceProvider::register()`.

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

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

You can also register `extend()` before the target's `bind()`. If the target has not been resolved and no instance is stored, registering alone does not build the original service. The official tests also cover lazy construction and registration before binding.

After that, `app(MessageFormatter::class)->format('Shipped.')` returns `[Acme] Shipped.`. Even if the original implementation is changed to another `MessageFormatter` implementation, the decorator wraps that implementation instead.

> [!WARNING]
>
> As a rule, register before the first resolution. Another provider may use the service first, so simply moving the registration to `boot()` does not prevent resolution before registration. Also, if you casually make a provider that only registers extensions deferred, the registration itself may lose the condition that triggers it.

## Understand where decorations apply and in what order

In a normal resolution, the container builds the original service, replaces it with each extender's return value in registration order, stores it as a shared instance, and then calls the after-resolving hooks.

```mermaid theme={null}
flowchart LR
    A["Original construction"] --> B["Service"]
    B --> C["extender A<br>Wrap with Wrapper A"]
    C --> D["extender B<br>Wrap with Wrapper B"]
    D --> E["Store if shared"]
    E --> F["After-resolving hooks and consumers"]
```

If you register A and then B, the result is `B(A(original service))`. Consumer method calls start at the outer B, so do not confuse registration order with the order in which processing starts. If you depend on the decoration order of several packages, make the order explicit in the consuming application and test the combination.

Extenders apply to the **resolution key** after the container normalizes aliases. A registration on `MessageFormatter::class` is not applied when `PlainMessageFormatter::class` is resolved directly just because the types match. Standardize on using the interface.

## Decorating a stored instance is retained differently

`Container::extend()` in `v13.40.0` branches on whether an instance of the target is stored in the container.

| State at registration | What `extend()` does | Retained for future builds |
| - | - | - |
| Not resolved yet and no stored instance | Adds to the extender array | Applied to later normal builds |
| A normal `bind()` was resolved before, but no instance is stored | Adds the extender and tries to send a rebound notification | Applied to later normal builds |
| A singleton or `instance()` object is stored | Decorates it immediately, replaces the stored value with the return value, and sends a rebound notification | **This extender is not added to the array** |

The last row matters. If you decorate a resolved singleton and then call `forgetInstance()` or rebind the same key, **a decoration that was only applied on the spot** is not reapplied on the next build. This differs from registering in the extender array before the first resolution.

`scoped()` also uses stored instances, so do not assume a decoration registered late in the middle of a scope remains registered for the next scope. Check this together with [Service lifetime design](https://kawax.biz/en/advanced/package-service-lifetimes.md).

Also, if you later store an object directly with `instance()`, it does not go through the loop that applies extenders during normal builds. Including test substitutions, "it is registered, so the object I pass is decorated automatically" does not always hold.

### References already handed out are not replaced automatically

When you return a new wrapper, as in this example, changing the container's stored value does not change the original object already stored in another service's property. A `rebinding()` notification passes the new service to registered callbacks. It does not search for and rewrite every dependent.

Registering a notification callback with `rebinding()` is not always lazy either. If the target is already bound, it returns the result of `make()` at registration time too. On the path where you `extend()` a normal binding that was resolved before, `make()` may also be called for the notification if a rebound callback exists. Do not uniformly assume that initialization and side effects happen "on next use".

## Avoid double decoration and carried-over state

`extend()` does not automatically deduplicate registrations of the same closure or the same decorator. If this provider's `register()` is applied twice to the same container, the prefix may be added twice as well.

* Keep decoration registration in the provider's fixed boot path, and do not repeat it per request or job.
* Do not prevent duplicates with a single static flag shared by all containers. Tests and new applications would miss registrations they need.
* A singleton's decoration is retained for the same shared lifetime. Do not pin the current user, request or similar state into the wrapper.
* `forgetExtenders()` only clears the registration array. It does not turn already built wrappers back into the original.

This example changes the output string. The prefix, the number of delegated calls and the exception handling also become public behavior of the package, so check compatibility instead of treating them as mere "internal implementation changes".

## Test registration timing with the same container

The following PHPUnit example assumes the classes above can be loaded through the package's autoload. It applies the provider's `register()` directly to a Container and checks how long the decoration is retained without HTTP or external services. Verify auto-discovery and full application boot separately with [package testing](https://kawax.biz/en/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'));
    }
}
```

The last test does not recommend late registration. It makes the boundary in the checked version explicit. Do not design `forgetInstance()` alone as an API for removing or re-registering decorations.

Before a release, also check the order of multiple extenders, duplicate registrations, substitution with `instance()`, and references consumers already hold. When you add supported Laravel versions, reread the branches in `extend()` and the official tests, in addition to the resolution keys and type contracts.

## Related pages

> [card] [Service resolution hooks](https://kawax.biz/en/advanced/package-service-resolution.md)
>
> Covers callAfterResolving(), which configures objects without replacing them, and what to watch for in how often it runs.

> [card] [Service lifetime design](https://kawax.biz/en/advanced/package-service-lifetimes.md)
>
> Covers bind, singleton and scoped, and the dependencies that built services hold on to.

## Primary sources

* [Official Laravel documentation: Extending bindings and Rebinding](https://github.com/laravel/docs/blob/13.x/container.md#extending-bindings)
* [Official Laravel documentation: Service Providers](https://github.com/laravel/docs/blob/13.x/providers.md)
* [Official Laravel documentation: Package development and 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)
* [Official tests: lazy construction, registration before binding, aliases, multiple extenders](https://github.com/laravel/framework/blob/v13.40.0/tests/Container/ContainerExtendTest.php)
* [ServiceProvider: constructor and register()](https://github.com/laravel/framework/blob/v13.40.0/src/Illuminate/Support/ServiceProvider.php)


## Related topics

- [Package service resolution hooks](https://kawax.biz/en/advanced/package-service-resolution.md)
- [Advanced Topics](https://kawax.biz/en/advanced/index.md)
- [Laravel Package Development](https://kawax.biz/en/advanced/package-development.md)
- [Extending database connections in packages](https://kawax.biz/en/advanced/package-database-connections.md)
- [Extending and re-resolving log channels in packages](https://kawax.biz/en/advanced/package-logging.md)


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