Naar hoofdinhoud gaan
Soms wil je dat gebruikers van je package de benodigde waarden kunnen laten injecteren zonder configuratiesleutels in een serviceprovider op te sommen. Door contextuele attributen aan te bieden, kun je packagespecifieke configuratie of services resolven op basis van metadata die aan een parameter is gekoppeld. Op deze pagina bekijken we de officiële broncode van Laravel Framework v13.39.0 en implementeren we een eigen attribuut dat de courier-configuratie leest. Als het ingebouwde #[Config('courier.timeout')] al volstaat, hoef je geen extra eigen attributen te maken. Overweeg dit pas wanneer je eigen sleutelconventies of een eigen resolutiebeleid als API van je package wilt aanbieden.

De rol van een attribuut is bepalen welke waarde wordt geïnjecteerd

Attributen die Illuminate\Contracts\Container\ContextualAttribute implementeren, worden door de Laravel-container gelezen wanneer die parameters resolvet. Niet het attribuutobject zelf, maar de waarde die de resolver teruggeeft, wordt aan de gebruikende klasse doorgegeven. Zie Service container voor gewone bindings en ingebouwde attributen. Contextuele attributen zijn een ander mechanisme dan attributen als Bind, Singleton en Scoped, die bindings voor klassen of interfaces declareren.

Een attribuut maken dat packageconfiguratie leest

Standaardconfiguratie registreren

Zet eerst standaardwaarden klaar in config/courier.php van het package.
Voeg de configuratie samen in register() van src/CourierServiceProvider.php. Zie De interne structuur van package discovery voor het automatisch registreren van providers, en Packageconfiguratie samenvoegen en cachen voor publiceren en de configuratiecache.

ContextualAttribute implementeren

Definieer in src/Attributes/CourierConfig.php een attribuut dat courier. voor de configuratiesleutel plaatst.
In de gecontroleerde implementatie krijgt de resolver de volgende drie argumenten:
  1. De instantie van het attribuut dat aan de parameter is gekoppeld.
  2. De container die de afhankelijkheid resolvet.
  3. Een ReflectionParameter die het te resolven doel voorstelt.
In dit voorbeeld wordt de parameternaam gebruikt als de sleutel wordt weggelaten. #[CourierConfig(default: 30)] int $timeout verwijst naar courier.timeout. Wil je dezelfde configuratie lezen, ook als de parameternaam aan de gebruikende kant verandert, geef de sleutel dan expliciet op, zoals #[CourierConfig('timeout', default: 30)]. ContextualAttribute is op het moment van controle een marker-interface die geen methoden declareert. implements alleen garandeert dus niet dat er een resolver bestaat of welke signatuur die heeft. Als je zoals hierboven een resolve() voorziet, hoef je voor dit attribuut geen gewone bind() te registreren.
Gebruik de constructor van het attribuut alleen om metadata vast te houden. De container roept newInstance() afzonderlijk aan bij het resolven en bij de nabewerking van het attribuut, dus het is geen plek voor “initialisatie die maar één keer draait”. Laat het attribuut geen externe communicatie doen of veranderlijke state vasthouden.

De gebruikende klasse via de container aanmaken

Voeg het attribuut toe in src/CourierOptions.php.
Als de gebruikende applicatie de configuratie overschrijft, wordt die waarde bij de volgende resolutie geïnjecteerd.
In dit voorbeeld is $options->timeout gelijk aan 45 en $options->retries gelijk aan 3. Het attribuut haalt alleen de configuratie op en voert geen bedrijfsmatige validatie uit, zoals of het een positief geheel getal is of binnen het toegestane maximum valt. Ook typedeclaraties vervangen geen validatie van configuratiewaarden.

De resolutievolgorde in de constructor lezen

Container::resolveDependencies() bepaalt de waarde van elke parameter in de volgende volgorde:
  1. Gebruik een overschrijfwaarde die bijvoorbeeld via makeWith() is meegegeven en overeenkomt met de parameternaam.
  2. Zoek met Util::getContextualAttributeFromDependency() naar een contextueel attribuut en resolve het.
  3. Als het resultaat van het attribuut null is, ga verder met de gewone resolutie van primitieve waarden of klassen.
  4. Geef na de nabewerking van het attribuut de bepaalde waarde door aan de constructor.
Het overschrijfpad uit stap 1 slaat de attribuutresolutie en de nabewerking voor die parameter over. De sleutels van makeWith() zijn parameternamen van de gebruikende klasse, geen configuratiesleutels.
In dit geval is timeout altijd 5, ongeacht de configuratiewaarde. Geeft het attribuut daarentegen iets anders dan null terug, dan heeft het resultaat van het attribuut voorrang op een gewone when()->needs('$timeout')->give(...). Test daarom ook of het toevoegen van een attribuut aan je package de contextuele bindings van bestaande gebruikers kan veranderen. Util haalt via Reflection de attributen op die ContextualAttribute implementeren en kiest de eerste daarvan voor de resolutie. Ook als je meerdere verschillende contextuele attributen naast elkaar zet, worden hun resolvers niet na elkaar gecombineerd.

Geregistreerde handlers hebben voorrang op resolve()

Container::resolveFromAttribute() zoekt eerst, met de klassenaam van het attribuut als sleutel, naar een handler die via whenHasAttribute() is geregistreerd. Is die er niet, dan wordt resolve() van het attribuut gebruikt, en als geen van beide bestaat, wordt een BindingResolutionException gegooid. whenHasAttribute() is een instelling per attribuutsoort, niet per gebruikende klasse. Opnieuw registreren voor dezelfde attribuutklasse overschrijft de vorige handler. Als een package onvoorwaardelijk de handlers van andere packages of van ingebouwde Laravel-attributen wijzigt, raakt dat de hele gebruikende applicatie. Leg het standaardgedrag van je eigen attribuut vast in resolve() en behandel wijzigingen via handlers als een expliciet uitbreidingspunt.

make(), call() en directe aanroepen zijn niet hetzelfde

Ook app()->call() resolvet attributen, maar het gewone BoundMethod-pad voegt het resultaat direct als argument toe zodra een attribuut wordt gevonden. Anders dan bij de constructorresolutie valt het niet terug op de resolutie van de standaardwaarde, ook niet als het resultaat van het attribuut null is. Hier een voorbeeld dat de niet-bestaande courier.missing leest, zonder andere contextuele bindings.
Ook new CourierOptions(timeout: 5, retries: 1) is gewone aanmaak via PHP. Alleen een attribuut toevoegen voegt geen dependency injection toe aan een directe new of methodeaanroep.
Heb je zowel in constructors als in callables hetzelfde standaardgedrag nodig, vertrouw dan niet alleen op de standaardwaarde van de parameter, maar definieer de standaard aan de kant van het attribuut, zoals #[CourierConfig('timeout', default: 30)]. Deze default wordt gebruikt als de configuratiesleutel niet bestaat; hij vervangt niet de waarde van een bestaande sleutel die null is. Verifieer bij een API die null zelf als betekenisvolle waarde behandelt beide resolutiepaden afzonderlijk.

Opnieuw injecteren van waarden en de levensduur van services scheiden

Een attribuut leest de configuratie op het moment dat de parameter wordt geresolved. Het is geen functie die de eigenschappen van een al aangemaakte CourierOptions bewaakt of bijwerkt. Als je CourierOptions bijvoorbeeld als singleton registreert, houdt die instantie de waarden vast die als eerste zijn geïnjecteerd. Ook als je daarna de configuratie wijzigt en een gewone make() aanroept, veranderen de waarden van dezelfde instantie niet. Ook wanneer je een attribuut maakt dat per-requestinformatie leest, bepaalt het attribuut zelf niet de singleton- of scoped-levensduur. Kies de levensduur van de service die de geïnjecteerde waarden of objecten vasthoudt volgens Levensduur van packageservices ontwerpen. Beschouw het opnieuw opbouwen van de configuratiecache los van het bijwerken van langlevende processen of al aangemaakte services.

De resolutiepaden testen

Tests voor het attribuut zelf kun je, net als de officiële tests, schrijven met een nieuwe Container en Illuminate\Config\Repository. Het volgende PHPUnit-voorbeeld gaat uit van een testomgeving waarin de hierboven gedefinieerde CourierConfig, CourierOptions en NullableTimeoutOptions zijn geladen.
Het voorbeeld waarin de handler altijd 7 teruggeeft, is uitsluitend bedoeld om de voorrang te testen. Binnen deze container beïnvloedt het alle resoluties van hetzelfde attribuut, inclusief retries. Met deze unittests alleen controleer je niet de integratie met de providerregistratie van het package, mergeConfigFrom() of de configuratiecache. Verifieer ook in de applicatieomgeving van Laravel-packages testen met Orchestra Testbench de toestanden vóór publicatie, na publicatie en met configuratiecache.

Controlepunten vóór een release

  • Behandel de klassenaam van het attribuut, de namen van de constructorparameters, de configuratiesleutels en het returntype als publieke API. Let ook op wijzigingen in key en default, die als benoemde argumenten worden gebruikt.
  • Als de parameternaam wordt gebruikt wanneer de sleutel ontbreekt, controleer dan of het hernoemen van een parameter in de gebruikende klasse niet verandert welke configuratie wordt gelezen.
  • Test afzonderlijk bestaande bindings zonder attribuut, resolutie met attribuut en overschrijven via de parameternaam.
  • Test null, ontbrekende configuratie en ongeschikte types zowel met make() als met call().
  • Controleer dat het ontwerp singletons en langlevende workers geen verouderde waarden laat vasthouden.
  • Controleer bij het bijwerken van de ondersteunde Laravel-versie opnieuw de argumenten van de resolver, de voorrangsvolgorde en de nabewerking van attributen aan de hand van de officiële broncode en tests.

Gerelateerde pagina’s

Packageconfiguratie samenvoegen en cachen

Bekijk de grenzen tussen standaardconfiguratie, gepubliceerde configuratie en het bijwerken van de cache.

Levensduur van packageservices ontwerpen

Kies de levensduur van services die geïnjecteerde waarden vasthouden.

Containertags in packages en het bundelen van plugins

Bekijk het ingebouwde Tag-attribuut en het bundelen van geregistreerde implementaties.

PHP Reflection API

Bekijk hoe metadata van attributen en parameters wordt uitgelezen.

Geraadpleegde primaire bronnen

Laatst gewijzigd op 31 oktober 2026