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 ensrc/MessageFormatter.php. Las clases siguientes también se ubican cada una en un archivo PHP con el mismo nombre dentro de src/.
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.
$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().
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.
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 esB(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 derebinding() 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.
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 elregister() 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.
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
- Documentación oficial de Laravel: Extending bindings y Rebinding
- Documentación oficial de Laravel: Service Providers
- Documentación oficial de Laravel: desarrollo de paquetes y detección automática
- Container: extend(), resolve(), rebound(), instance(), forgetExtenders()
- Pruebas oficiales: creación diferida, registro antes del binding, alias, varios extenders
- ServiceProvider: constructor y register()