Saltar al contenido principal
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, 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

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.
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/.
La implementación sin decorar devuelve la entrada tal cual.
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.
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.
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().
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.
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. 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. 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. 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.
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

Hooks de resolución de servicios en paquetes

Revisa callAfterResolving(), que configura objetos sin reemplazarlos, y las precauciones sobre cuántas veces se aplica.

Diseño del ciclo de vida de los servicios de paquetes

Revisa bind, singleton y scoped, y las dependencias que conservan los servicios ya creados.

Fuentes primarias consultadas

Última modificación el 9 de noviembre de 2026