v13.39.0 y se implementa un atributo propio que lee la configuración courier. Si el atributo integrado #[Config('courier.timeout')] basta para tu objetivo, no hace falta añadir atributos propios. Considéralos cuando quieras ofrecer, como parte de la API del paquete, una convención de claves o una política de resolución propias.
La función del atributo es decidir qué valor se inyecta
El contenedor de Laravel lee los atributos que implementanIlluminate\Contracts\Container\ContextualAttribute al resolver los argumentos. A la clase consumidora no se le pasa el objeto del atributo, sino el valor que devuelve el resolver.
Para los bindings normales y los atributos integrados, consulta Service container. Los atributos contextuales son un mecanismo distinto de los atributos que declaran bindings de clases o interfaces, como
Bind, Singleton y Scoped.
Crear un atributo que lee la configuración del paquete
Registrar la configuración predeterminada
Primero, prepara los valores predeterminados en elconfig/courier.php del paquete.
register() de src/CourierServiceProvider.php. Para el registro automático del provider, consulta Estructura interna de la detección automática de paquetes, y para la publicación y la caché de configuración, Fusión y caché de la configuración de paquetes.
Implementar ContextualAttribute
En src/Attributes/CourierConfig.php, define un atributo que añade courier. a la clave de configuración.
- La instancia del atributo añadido al argumento.
- El contenedor que está resolviendo la dependencia.
- El
ReflectionParameterque representa el objetivo de la resolución.
#[CourierConfig(default: 30)] int $timeout hace referencia a courier.timeout. Si quieres leer la misma configuración aunque cambie el nombre del argumento en el lado consumidor, indica la clave de forma explícita, como en #[CourierConfig('timeout', default: 30)].
En el momento de la revisión, ContextualAttribute es una interfaz marcadora que no declara métodos. Por eso, implements por sí solo no garantiza la existencia del resolver ni su firma. Si defines resolve() como arriba, no necesitas registrar un bind() normal para este atributo.
Crear la clase consumidora desde el contenedor
Añade los atributos ensrc/CourierOptions.php.
$options->timeout es 45 y $options->retries es 3. El atributo solo obtiene la configuración; no realiza validaciones de negocio, como comprobar si es un entero positivo o si respeta el máximo permitido. La declaración de tipo tampoco sustituye a la validación de los valores de configuración.
Leer el orden de resolución en el constructor
Container::resolveDependencies() decide el valor de cada argumento en este orden:
- Usa el valor de sobrescritura que coincide con el nombre del argumento, pasado con
makeWith()u otros métodos. - Busca un atributo contextual con
Util::getContextualAttributeFromDependency()y lo resuelve. - Si el resultado del atributo es
null, pasa a la resolución normal de valores primitivos o de clases. - Tras el posprocesamiento del atributo, pasa el valor decidido al constructor.
makeWith() son los nombres de los argumentos de la clase consumidora, no las claves de configuración.
timeout es 5 sin importar la configuración. En cambio, si el atributo devuelve algo distinto de null, su resultado tiene prioridad sobre el when()->needs('$timeout')->give(...) normal. Prueba también la posibilidad de que añadir un atributo al paquete cambie los bindings contextuales de los usuarios existentes.
Util obtiene mediante Reflection los atributos que implementan ContextualAttribute y elige el primero para la resolución. Aunque coloques varios atributos contextuales distintos, sus resolvers no se combinan en orden.
Los handlers registrados tienen prioridad sobre resolve()
Container::resolveFromAttribute() usa el nombre de la clase del atributo como clave y busca primero un handler registrado con whenHasAttribute(). Si no lo hay, usa el resolve() del atributo, y si no existe ninguno de los dos, lanza una BindingResolutionException.
whenHasAttribute() es una configuración por tipo de atributo, no por clase consumidora. Volver a registrar la misma clase de atributo sobrescribe el handler anterior. Si un paquete modifica sin condiciones los handlers de otros paquetes o de los atributos integrados de Laravel, afecta a toda la aplicación consumidora. Coloca el comportamiento estándar de tus atributos propios en resolve() y trata los cambios mediante handlers como un punto de extensión explícito.
make(), call() y la llamada directa no son lo mismo
app()->call() también resuelve atributos, pero la vía normal de BoundMethod, cuando encuentra un atributo, añade su resultado directamente a los argumentos. A diferencia de la resolución del constructor, aunque el resultado del atributo sea null, no vuelve a la resolución del valor predeterminado.
Este ejemplo lee la clave inexistente courier.missing sin ningún otro binding contextual.
new CourierOptions(timeout: 5, retries: 1) también es una creación normal de PHP. Añadir un atributo no agrega inyección de dependencias a un new directo ni a las llamadas a métodos.
Si necesitas el mismo comportamiento predeterminado tanto en el constructor como en los callables, no dependas solo del valor predeterminado del argumento y define el valor predeterminado en el atributo, como en
#[CourierConfig('timeout', default: 30)]. Este default se usa cuando la clave de configuración no existe y no reemplaza el valor cuando una clave existente vale null. En las API que tratan el propio null como un valor con significado, verifica por separado las dos vías de resolución.Separar la reinyección de valores del ciclo de vida del servicio
El atributo lee la configuración en el momento en que se resuelve el argumento. No es una función que observe o actualice las propiedades de unCourierOptions ya creado.
Por ejemplo, si CourierOptions es un singleton, esa instancia conserva los valores inyectados la primera vez. Aunque después cambies la configuración y llames a make() normalmente, los valores de la misma instancia no cambian.
Incluso al crear atributos que leen información por solicitud, el atributo no decide por sí mismo un ciclo de vida singleton o scoped. Elige el ciclo de vida de los servicios que conservan los valores u objetos inyectados según Diseño del ciclo de vida de los servicios de paquetes. Considera también por separado la regeneración de la caché de configuración y la actualización de procesos de larga duración y servicios ya creados.
Probar las vías de resolución
Las pruebas del atributo por sí solo se pueden escribir, como en las pruebas oficiales, con un nuevoContainer y un Illuminate\Config\Repository. El siguiente ejemplo de PHPUnit supone un entorno de pruebas que carga CourierConfig, CourierOptions y NullableTimeoutOptions, definidos arriba.
7 sirve solo para probar el orden de prioridad. Dentro de este contenedor, afecta a todas las resoluciones del mismo atributo, incluida retries.
Esta prueba unitaria por sí sola no comprueba la integración con el registro del provider del paquete, mergeConfigFrom() ni la caché de configuración. Verifica también los estados antes de publicar, después de publicar y con caché de configuración en el entorno de aplicación de Probar paquetes Laravel con Orchestra Testbench.
Comprobaciones antes de publicar una versión
- Trata como API pública el nombre de la clase del atributo, los nombres de los argumentos del constructor, las claves de configuración y el tipo de retorno. Presta atención también a los cambios en
keyydefault, que se usan como argumentos con nombre. - Si la especificación usa el nombre del argumento cuando se omite la clave, comprueba que cambiar el nombre del argumento en la clase consumidora no cambie la configuración a la que se hace referencia.
- Prueba por separado los bindings existentes sin atributo, la resolución con atributo y la sobrescritura por nombre de argumento.
- Prueba
null, la configuración ausente y los tipos inadecuados tanto conmake()como concall(). - Comprueba que el diseño no haga que los singletons o los workers de larga duración conserven valores obsoletos.
- Al actualizar las versiones de Laravel compatibles, vuelve a revisar los argumentos del resolver, el orden de prioridad y el posprocesamiento de atributos en el código fuente y las pruebas oficiales.
Páginas relacionadas
Fusión y caché de la configuración de paquetes
Revisa la configuración predeterminada, la configuración publicada y los límites de actualización de la caché.
Diseño del ciclo de vida de los servicios de paquetes
Elige el ciclo de vida de los servicios que conservan los valores inyectados.
Etiquetas del contenedor en paquetes y agregación de plugins
Revisa el atributo integrado Tag y la agregación de implementaciones registradas.
PHP Reflection API
Revisa el mecanismo para leer los metadatos de atributos y argumentos.
Fuentes primarias consultadas
- Documentación oficial de Laravel: Defining custom attributes
- Documentación oficial de Laravel: configuración de paquetes
- Container: resolveDependencies(), resolveFromAttribute(), whenHasAttribute() y posprocesamiento de atributos
- BoundMethod: resolución de argumentos de callables
- Util: detección de ContextualAttribute mediante Reflection
- ContextualAttribute: interfaz marcadora
- Config: atributo de configuración integrado
- Config Repository: get() y obtención de valores de configuración
- Arr: valor predeterminado de get() y tratamiento de null existente
- Pruebas oficiales: resolver del atributo, handlers registrados, ReflectionParameter y resolución con call