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

# Decoración de servicios y extend en paquetes

> Analiza Container::extend() de Laravel 13 para aprender decoradores que mantienen el contrato, el registro antes y después de la resolución, cuánto se conserva la decoración de un singleton, la doble decoración y las pruebas al actualizar.

A veces un paquete necesita añadir comportamiento manteniendo la implementación del servicio que eligió la aplicación consumidora. Con `extend()` del contenedor puedes envolver el servicio resuelto en un decorador sin redefinir la lógica de creación original.

Esta página se basa en la rama predeterminada `13.x` de la documentación oficial de Laravel y en la implementación y las pruebas oficiales de la última versión del Framework, `v13.40.0`, revisadas el 9 de noviembre de 2026. Además de la diferencia con los [hooks de resolución de servicios](https://kawax.biz/es/advanced/package-service-resolution.md), explica cuánto se conserva una decoración, lo que cambia según **si ya hay una instancia guardada en el momento del registro**.

## Separa añadir configuración de reemplazar el objeto

| API | Propósito | Valor de retorno del callback |
| - | - | - |
| `bind()`, `singleton()` | Registrar cómo se crea un servicio y si se comparte | El servicio que se crea |
| `afterResolving()` | Aplicar configuración y similares al servicio resuelto | No se usa para reemplazar el servicio |
| `extend()` | Mantener la lógica de creación y decorar o modificar el servicio | Siempre debe devolver el servicio modificado |

El callback de `extend()` recibe el servicio objetivo y el contenedor. Puedes configurar el mismo objeto y devolverlo, o devolver un decorador nuevo. Si olvidas el `return`, se pasa `null` al resto del proceso de resolución.

> [!NOTE]
>
> Aquí se trata `Illuminate\Container\Container::extend()`. Es una API distinta del `extend()` de los managers como Cache o Mail, que registra un nombre de driver y su lógica de creación.

## Crea un decorador que implemente el mismo contrato

### Envuelve la implementación original sin reemplazarla

Como ejemplo, coloca el siguiente contrato para formatear mensajes en `src/MessageFormatter.php`. Las clases siguientes también se ubican cada una en un archivo PHP con el mismo nombre dentro de `src/`.

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

namespace Acme\Messages;

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

La implementación sin decorar devuelve la entrada tal cual.

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

namespace Acme\Messages;

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

El decorador implementa la misma interfaz y delega en el servicio que recibe. En este ejemplo, añade un prefijo propio del paquete al valor que devuelve el delegado.

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

Si los consumidores tipan la clase concreta `PlainMessageFormatter`, un decorador que solo implementa la misma interfaz no puede sustituirla. Expón `MessageFormatter` como dependencia y mantén el contrato de argumentos, valores de retorno, excepciones, etc. El reenvío dinámico de métodos por sí solo no sustituye la implementación de una interfaz PHP.

### Registra la decoración en un provider

`src/PrefixServiceProvider.php`, que proporciona la decoración, no sobrescribe cómo se crea el servicio. Solo registra una extensión en el contenedor. Para la carga del provider, consulta [Estructura interna de la detección automática de paquetes](https://kawax.biz/es/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),
        );
    }
}
```

El callback también recibe el contenedor como segundo argumento, pero este ejemplo no lo necesita, así que no se declara. Usa el argumento `$formatter` como servicio objetivo. Volver a llamar a `make()` con la misma clave dentro del callback puede causar una resolución circular.

Vincula la implementación original en la aplicación consumidora, por ejemplo en `AppServiceProvider::register()`.

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

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

También puedes registrar `extend()` antes del `bind()` del objetivo. Si el objetivo no se ha resuelto y no hay instancia guardada, el registro por sí solo no crea el servicio original. Las pruebas oficiales también verifican la creación diferida y el registro antes del binding.

Después, `app(MessageFormatter::class)->format('Enviado.')` devuelve `[Acme] Enviado.`. Aunque cambies la implementación original por otra implementación de `MessageFormatter`, el decorador envuelve esa implementación.

> [!WARNING]
>
> Como norma, registra antes de la primera resolución. Otro provider podría usar el servicio antes, así que mover el registro a `boot()` no evita la resolución previa al registro. Además, si conviertes a la ligera en diferido un provider que solo registra extensiones, el propio registro puede quedarse sin condición de arranque.

## Entiende dónde se aplica la decoración y en qué orden

En una resolución normal, el contenedor crea el servicio original, lo reemplaza por el valor de retorno de cada extender en orden de registro, lo guarda como instancia compartida y luego llama a los hooks posteriores a la resolución.

```mermaid theme={null}
flowchart LR
    A["Creación original"] --> B["Servicio"]
    B --> C["extender A<br>Envolver con Wrapper A"]
    C --> D["extender B<br>Envolver con Wrapper B"]
    D --> E["Guardar si es compartido"]
    E --> F["Hooks posteriores y consumidores"]
```

Si registras A y luego B, el resultado es `B(A(servicio original))`. Las llamadas a métodos de los consumidores empiezan por la B exterior, así que no confundas el orden de registro con el orden en que empieza el procesamiento. Si dependes del orden de decoración de varios paquetes, explicita el orden en la aplicación consumidora y prueba la combinación.

Los extenders se aplican a la **clave de resolución** después de que el contenedor normaliza los alias. Un registro en `MessageFormatter::class` no se aplica al resolver `PlainMessageFormatter::class` directamente solo porque los tipos coincidan. Usa siempre la interfaz.

## La decoración de una instancia guardada se conserva de otra forma

`Container::extend()` en `v13.40.0` se ramifica según si hay una instancia del objetivo guardada en el contenedor.

| Estado al registrar | Comportamiento de `extend()` | Conservado para futuras creaciones |
| - | - | - |
| Aún no resuelto y sin instancia guardada | Añade el extender al array | Se aplica en las creaciones normales posteriores |
| Un `bind()` normal se resolvió antes, pero no hay instancia guardada | Añade el extender e intenta enviar una notificación rebound | Se aplica en las creaciones normales posteriores |
| Hay guardado un objeto singleton o de `instance()` | Lo decora en el acto, reemplaza el valor guardado por el valor de retorno y envía una notificación rebound | **Este extender no se añade al array** |

La última fila es importante. Si decoras un singleton ya resuelto y luego llamas a `forgetInstance()` o vuelves a vincular la misma clave, **una decoración aplicada solo en el acto** no se vuelve a aplicar en la siguiente creación. Esto es distinto de registrarla en el array de extenders antes de la primera resolución.

`scoped()` también usa instancias guardadas, así que no supongas que una decoración registrada tarde a mitad de un scope sigue registrada para el siguiente scope. Revísalo junto con el [diseño del ciclo de vida de los servicios de paquetes](https://kawax.biz/es/advanced/package-service-lifetimes.md).

Además, si más tarde guardas un objeto directamente con `instance()`, no pasa por el bucle que aplica los extenders en las creaciones normales. Incluso en los reemplazos de pruebas, no siempre se cumple que "como está registrado, el objeto que paso se decora automáticamente".

### Las referencias ya repartidas no se reemplazan automáticamente

Cuando devuelves un wrapper nuevo, como en este ejemplo, aunque cambie el valor guardado en el contenedor, el objeto original ya guardado en una propiedad de otro servicio no cambia. La notificación de `rebinding()` pasa el nuevo servicio a los callbacks registrados. No busca ni reescribe todas las dependencias.

Registrar un callback de notificación con `rebinding()` tampoco es siempre diferido. Si el objetivo ya está vinculado, también devuelve el resultado de `make()` en el momento del registro. En la ruta en la que aplicas `extend()` a un binding normal resuelto previamente, también se puede llamar a `make()` para la notificación si existe un callback rebound. No des por hecho de forma general que la inicialización y los efectos secundarios ocurren "en el siguiente uso".

## Evita la doble decoración y el estado arrastrado

`extend()` no elimina automáticamente los registros duplicados del mismo closure o del mismo decorador. Si el `register()` de este provider se aplica dos veces al mismo contenedor, el prefijo también puede duplicarse.

* Concentra el registro de decoraciones en la ruta de arranque fija del provider y no lo repitas por cada solicitud o job.
* No evites duplicados con una única bandera estática compartida por todos los contenedores. Las pruebas y las aplicaciones nuevas perderían registros necesarios.
* La decoración de un singleton se conserva durante el mismo periodo compartido. No fijes en el wrapper el usuario actual, la solicitud ni estados similares.
* `forgetExtenders()` solo borra el array de registros. No devuelve los wrappers ya creados a su estado original.

Este ejemplo modifica la cadena de salida. El prefijo, el número de llamadas delegadas y el manejo de excepciones también pasan a ser comportamiento público del paquete, así que no los trates como un simple "cambio interno de implementación" y comprueba la compatibilidad.

## Prueba el momento del registro con el mismo contenedor

El siguiente ejemplo de PHPUnit supone que las clases anteriores se pueden cargar mediante el autoload del paquete. Aplica directamente el `register()` del provider a un Container y comprueba cuánto se conserva la decoración sin usar HTTP ni servicios externos. Verifica la detección automática y el arranque completo de la aplicación por separado con [Probar paquetes Laravel con Orchestra Testbench](https://kawax.biz/es/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'));
    }
}
```

La última prueba no recomienda el registro tardío, sino que deja explícito el límite en la versión revisada. No diseñes `forgetInstance()` por sí solo como API para quitar o volver a registrar decoraciones.

Antes de publicar una versión, comprueba también el orden de varios extenders, los registros duplicados, el reemplazo con `instance()` y las referencias que los consumidores ya tienen. Cuando añadas versiones de Laravel compatibles, vuelve a leer las ramas de `extend()` y las pruebas oficiales, además de las claves de resolución y los contratos de tipos.

## Páginas relacionadas

> [card] [Hooks de resolución de servicios en paquetes](https://kawax.biz/es/advanced/package-service-resolution.md)
>
> Revisa callAfterResolving(), que configura objetos sin reemplazarlos, y las precauciones sobre cuántas veces se aplica.

> [card] [Diseño del ciclo de vida de los servicios de paquetes](https://kawax.biz/es/advanced/package-service-lifetimes.md)
>
> Revisa bind, singleton y scoped, y las dependencias que conservan los servicios ya creados.

## Fuentes primarias consultadas

* [Documentación oficial de Laravel: Extending bindings y Rebinding](https://github.com/laravel/docs/blob/13.x/container.md#extending-bindings)
* [Documentación oficial de Laravel: Service Providers](https://github.com/laravel/docs/blob/13.x/providers.md)
* [Documentación oficial de Laravel: desarrollo de paquetes y detección automática](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)
* [Pruebas oficiales: creación diferida, registro antes del binding, alias, varios extenders](https://github.com/laravel/framework/blob/v13.40.0/tests/Container/ContainerExtendTest.php)
* [ServiceProvider: constructor y register()](https://github.com/laravel/framework/blob/v13.40.0/src/Illuminate/Support/ServiceProvider.php)


## Related topics

- [Hooks de resolución de servicios en paquetes](https://kawax.biz/es/advanced/package-service-resolution.md)
- [Temas avanzados](https://kawax.biz/es/advanced/index.md)
- [Extensión y nueva resolución de canales de log en paquetes](https://kawax.biz/es/advanced/package-logging.md)
- [Stores de caché personalizados y contratos en paquetes](https://kawax.biz/es/advanced/package-cache-drivers.md)
- [Extensión de conexiones de base de datos en paquetes](https://kawax.biz/es/advanced/package-database-connections.md)


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