callAfterResolving() del service provider sirve para combinar ambas cosas.
En esta página se revisa la implementación de Laravel Framework v13.35.0 y se profundiza en la extensión desde boot() que aparece en Desarrollo de paquetes para Laravel. No es un hook que espere a que termine el arranque de toda la aplicación, sino un hook sobre la resolución del servicio indicado.
Esperar si no está resuelto y ejecutar ya si está resuelto
ServiceProvider::callAfterResolving() realiza el siguiente proceso en dos pasos.
- Registra el callback en
afterResolving()del contenedor. - Si
resolved()es true, obtiene el servicio conmake()y llama también al callback en ese momento.
make() sobre el destino. En una resolución normal, el contenedor construye el objeto, aplica los extenders, llama a los callbacks de resolving y después llama a los callbacks de afterResolving.
Al obtener un singleton de forma normal, el contenedor devuelve antes la instancia guardada, por lo que
afterResolving no se dispara en cada obtención. Lo importante es que, si solo registras afterResolving(), la configuración no se aplicará a un singleton creado antes del registro.
Añadir una regla al Factory de validación
Como ejemplo de un paquete que ofrece su propia regla de cadena, se registra la regla después de resolvervalidator. El ValidationServiceProvider oficial registra esta clave como singleton, y el propio provider admite carga diferida.
extend() de este ejemplo es la API de registro de reglas de Illuminate\Validation\Factory. Es un método distinto del extend() del contenedor que se describe más adelante. Las reglas personalizadas normales pueden no ejecutarse con valores vacíos, así que la obligatoriedad se indica con required. Para el diseño detallado de la regla en sí, consulta Reglas de validación personalizadas.
Factory::extend() sobrescribe el elemento del array con el mismo nombre de regla, así que, como en este ejemplo, volver a registrar el mismo proceso no añade más entradas. Aun así, para no sobrescribir reglas de otros paquetes, añade un prefijo propio del paquete a los nombres de las reglas públicas.
El callback se ejecuta cuando se resuelve el Factory. La closure de validación de la regla se ejecuta más tarde, cuando el Validator valida el valor. No se validan valores de entrada al registrar el hook.
Hacer coincidir la clave destino y la comprobación de resolución
En los eventos de resolución normales del contenedor, no solo se seleccionan los callbacks que coinciden con la clave registrada, sino también los que coinciden con el tipo del objeto obtenido. En cambio,resolved($name), que callAfterResolving() usa en ese momento, comprueba si existe una marca de resuelto o una instancia guardada para la clave normalizada a partir del alias. No es un proceso que busque entre todos los objetos ya creados.
Por eso, que una interfaz o clase esté resuelta no garantiza que resolved() devuelva true para otra clave. Revisa el binding real y los alias del servicio destino, y prueba también el caso ya resuelto con la misma clave. En el ejemplo anterior se indica validator, que es la clave registrada por el provider oficial.
Además, resolved() también incluye el criterio de «se resolvió en el pasado». No significa que la instancia actual siga existiendo necesariamente.
No es una API que garantice «ejecutar solo una vez»
callAfterResolving() deja el callback registrado. Incluso después de ejecutarse en ese momento, el callback registrado se ejecutará si el objeto se vuelve a resolver en el futuro. Además, si vuelves a llamar al método, se añade otro callback.
Ten especial cuidado si ya has resuelto un binding normal no compartido. En la implementación revisada, ocurre lo siguiente.
- Se registra un nuevo callback.
- Como
resolved()es true, se llama amake(). make()crea un nuevo objeto y el callback se ejecuta en su evento de resolución.- El callback inmediato también se ejecuta sobre el mismo objeto devuelto por
make().
Usar otra API para reemplazar el objeto
El valor de retorno de un callback deafterResolving no se usa para reemplazar el objeto que devuelve el contenedor. Si quieres devolver un decorador para sustituir el propio servicio, considera el extend() del contenedor. El contrato de la closure de esta API es devolver el servicio modificado.
Del mismo modo, callAfterResolving() tampoco es un mecanismo que actualice todas las dependencias ya guardadas en otros objetos. Si el objetivo es actualizar dependencias al volver a hacer un binding, revisa rebinding() en la documentación oficial y el diseño de la clase destino.
Para los requisitos de un provider que realmente cargue servicios de forma diferida, consulta DeferrableProvider. Usar el hook y hacer que el propio provider que lo registra se cargue de forma diferida son cosas distintas.
Combinaciones que comprobar al actualizar
Antes de publicar el paquete, verifica no solo el orden de arranque normal, sino también el caso en que otro provider haya usado el servicio antes.
Si registras con una bandera estática que algo ya se usó una vez en toda la aplicación, la configuración puede no aplicarse a servicios recreados en un nuevo contenedor o en las pruebas. En la medida de lo posible, garantiza la idempotencia de la configuración a nivel del objeto destino o de la clave registrada.
Páginas relacionadas
Sobrescritura y actualización de vistas de paquetes
Revisa un ejemplo en el que loadViewsFrom() usa un hook de resolución para registrar el espacio de nombres de las vistas.
Sobrescritura y actualización de traducciones de paquetes
Revisa el registro de espacios de nombres en el Translator y el tratamiento de las traducciones ya cargadas.
Fuentes primarias consultadas
- Documentación oficial de Laravel: desarrollo de paquetes
- Documentación oficial de Laravel: Container events, Extending bindings y Rebinding
- Documentación oficial de Laravel: reglas de validación personalizadas
- ServiceProvider: callAfterResolving() y su uso en el registro de recursos
- Container: resolved(), resolve() y afterResolving()
- ValidationServiceProvider: registro de validator como singleton
- Validation Factory: registro de reglas con extend()