Vai al contenuto principale
A volte si vuole permettere agli utenti di un pacchetto di iniettare i valori di cui hanno bisogno senza elencare chiavi di configurazione nel service provider. Esponendo un attributo contestuale, è possibile risolvere configurazioni o servizi specifici del pacchetto a partire dai metadati applicati a un parametro. Questa pagina esamina il sorgente ufficiale di Laravel Framework v13.39.0 e implementa un attributo personalizzato che legge la configurazione courier. Se il solo attributo integrato #[Config('courier.timeout')] è sufficiente per lo scopo, non serve aggiungere attributi personalizzati. Valutali quando vuoi offrire, come API del pacchetto, convenzioni proprie sulle chiavi o una politica di risoluzione specifica.

Il ruolo dell’attributo è decidere quale valore iniettare

Gli attributi che implementano Illuminate\Contracts\Container\ContextualAttribute vengono letti dal container di Laravel quando risolve i parametri. Alla classe che li usa non viene passato l’oggetto attributo in sé, ma il valore restituito dal resolver. Per i binding ordinari e gli attributi integrati consulta Service container. Gli attributi contestuali sono un meccanismo diverso dagli attributi che dichiarano binding di classi o interfacce, come Bind, Singleton e Scoped.

Creare un attributo che legge la configurazione del pacchetto

Registrare la configurazione predefinita

Per prima cosa, prepara i valori predefiniti in config/courier.php del pacchetto.
Unisci la configurazione nel metodo register() di src/CourierServiceProvider.php. Per la registrazione automatica del provider consulta Struttura interna del package auto-discovery, per la pubblicazione e la gestione della cache di configurazione consulta Merge e cache della configurazione dei pacchetti.

Implementare ContextualAttribute

In src/Attributes/CourierConfig.php definisci un attributo che antepone courier. alla chiave di configurazione.
Nell’implementazione esaminata, al resolver vengono passati i seguenti tre argomenti.
  1. L’istanza dell’attributo applicato al parametro.
  2. Il container che sta risolvendo la dipendenza.
  3. Il ReflectionParameter che rappresenta il parametro da risolvere.
In questo esempio, se la chiave viene omessa si usa il nome del parametro. #[CourierConfig(default: 30)] int $timeout fa riferimento a courier.timeout. Se vuoi leggere la stessa configurazione anche quando il nome del parametro cambia lato utente, specifica la chiave in modo esplicito, ad esempio #[CourierConfig('timeout', default: 30)]. Al momento della verifica, ContextualAttribute è un’interfaccia marcatore che non dichiara metodi. Per questo, il solo implements non garantisce l’esistenza né la firma del resolver. Se fornisci resolve() come sopra, non è necessario registrare un normale bind() per questo attributo.
Usa il costruttore dell’attributo solo per conservare metadati. Il container chiama newInstance() separatamente durante la risoluzione e durante la post-elaborazione dell’attributo, quindi non è il posto per un‘“inizializzazione da eseguire una sola volta”. Non inserire comunicazioni esterne né stato mutabile.

Creare la classe utente dal container

Applica l’attributo in src/CourierOptions.php.
Se l’applicazione utente sovrascrive la configurazione, quel valore viene iniettato alla risoluzione successiva.
In questo esempio $options->timeout vale 45 e $options->retries vale 3. L’attributo si limita a ottenere la configurazione e non esegue validazioni di dominio, come verificare che si tratti di un intero positivo o che non superi un valore massimo consentito. Nemmeno le dichiarazioni di tipo sostituiscono la validazione dei valori di configurazione.

Leggere l’ordine di risoluzione nel costruttore

Container::resolveDependencies() determina il valore di ciascun parametro nel seguente ordine.
  1. Usa il valore di override passato tramite makeWith() o simili, se corrisponde al nome del parametro.
  2. Cerca e risolve un attributo contestuale con Util::getContextualAttributeFromDependency().
  3. Se il risultato dell’attributo è null, passa alla normale risoluzione di valori primitivi o classi.
  4. Dopo la post-elaborazione dell’attributo, passa il valore determinato al costruttore.
Il percorso di override del punto 1 salta la risoluzione dell’attributo e la post-elaborazione per quel parametro. Le chiavi di makeWith() sono i nomi dei parametri della classe utente, non le chiavi di configurazione.
In questo caso timeout vale 5 indipendentemente dalla configurazione. D’altra parte, se l’attributo restituisce un valore diverso da null, il suo risultato ha la precedenza sul normale when()->needs('$timeout')->give(...). Verifica con i test anche la possibilità che l’aggiunta di un attributo al pacchetto modifichi i binding contestuali degli utenti esistenti. Util ottiene tramite Reflection gli attributi che implementano ContextualAttribute e sceglie il primo per la risoluzione. Anche se si affiancano più attributi contestuali diversi, i resolver non vengono composti in sequenza.

Gli handler registrati hanno la precedenza su resolve()

Container::resolveFromAttribute() usa il nome della classe dell’attributo come chiave e cerca prima un handler registrato con whenHasAttribute(). Se non ne trova, usa il metodo resolve() dell’attributo; se non esiste nessuno dei due, lancia una BindingResolutionException. whenHasAttribute() è una configurazione per tipo di attributo, non per classe utente. Registrare di nuovo la stessa classe di attributo sovrascrive l’handler precedente. Se un pacchetto modifica incondizionatamente gli handler di altri pacchetti o degli attributi integrati di Laravel, l’effetto si estende all’intera applicazione utente. Colloca il comportamento standard dell’attributo personalizzato in resolve() e tratta le modifiche tramite handler come un punto di estensione esplicito.

make(), call() e la chiamata diretta non sono la stessa cosa

Anche app()->call() risolve gli attributi, ma il normale percorso di BoundMethod, quando trova un attributo, aggiunge il risultato così com’è agli argomenti. A differenza della risoluzione del costruttore, anche se il risultato dell’attributo è null non torna alla risoluzione del valore predefinito. Ecco un esempio che legge la chiave inesistente courier.missing, in assenza di altri binding contestuali.
Anche new CourierOptions(timeout: 5, retries: 1) è una normale creazione in PHP. Il solo fatto di applicare un attributo non aggiunge l’iniezione delle dipendenze a un new diretto o alle chiamate di metodo.
Se ti serve lo stesso comportamento predefinito sia nel costruttore sia nelle callable, non affidarti solo al valore predefinito del parametro, ma definisci il valore predefinito lato attributo, ad esempio #[CourierConfig('timeout', default: 30)]. Questo default viene usato quando la chiave di configurazione non esiste e non sostituisce il valore di una chiave esistente che vale null. Nelle API che trattano null come un valore significativo, verifica separatamente i due percorsi di risoluzione.

Distinguere la reiniezione dei valori dalla durata dei servizi

L’attributo legge la configurazione nel momento in cui risolve il parametro. Non è una funzionalità che osserva o aggiorna le proprietà di un CourierOptions già creato. Ad esempio, se registri CourierOptions come singleton, i valori iniettati la prima volta restano conservati in quell’istanza. Anche se in seguito modifichi la configurazione e chiami un normale make(), i valori della stessa istanza non cambiano. Anche quando crei un attributo che legge informazioni legate alla singola richiesta, non è l’attributo stesso a determinare una durata singleton o scoped. Scegli la durata del servizio che conserva i valori o gli oggetti iniettati seguendo Progettare il ciclo di vita dei servizi di un pacchetto. Considera separatamente anche la rigenerazione della cache di configurazione e l’aggiornamento dei processi a lunga durata o dei servizi già creati.

Testare i percorsi di risoluzione

I test del solo attributo si possono scrivere, come nei test ufficiali, usando un nuovo Container e Illuminate\Config\Repository. Il seguente esempio PHPUnit presuppone un ambiente di test in cui siano caricati CourierConfig, CourierOptions e NullableTimeoutOptions definiti sopra.
L’esempio in cui l’handler restituisce sempre 7 serve solo a testare l’ordine di priorità. All’interno di questo container influisce su tutte le risoluzioni dello stesso attributo, incluso retries. Questo test unitario da solo non verifica l’integrazione con la registrazione del provider del pacchetto, con mergeConfigFrom() o con la cache di configurazione. Verifica anche, nell’ambiente applicativo descritto in Testare pacchetti Laravel con Orchestra Testbench, gli stati prima della pubblicazione, dopo la pubblicazione e con la cache di configurazione attiva.

Controlli prima del rilascio

  • Tratta come API pubblica il nome della classe dell’attributo, i nomi dei parametri del costruttore, le chiavi di configurazione e il tipo di ritorno. Fai attenzione anche alle modifiche di key e default, usati come argomenti con nome.
  • Se la specifica prevede l’uso del nome del parametro quando la chiave è omessa, verifica che rinominare un parametro nella classe utente non cambi la configurazione a cui si fa riferimento.
  • Testa separatamente i binding esistenti senza attributo, la risoluzione con attributo e l’override tramite nome del parametro.
  • Testa null, la configurazione mancante e i tipi non validi sia con make() sia con call().
  • Verifica che il design non faccia conservare valori obsoleti a singleton o worker a lunga durata.
  • Quando aggiorni le versioni di Laravel supportate, ricontrolla sul sorgente ufficiale e sui test gli argomenti del resolver, l’ordine di priorità e la post-elaborazione degli attributi.

Pagine correlate

Merge e cache della configurazione dei pacchetti

Verifica la configurazione predefinita, la configurazione pubblicata e i confini di aggiornamento della cache.

Progettare il ciclo di vita dei servizi di un pacchetto

Scegli la durata dei servizi che conservano i valori iniettati.

Tag del container e aggregazione di plugin nei pacchetti

Verifica l’attributo integrato Tag e l’aggregazione delle implementazioni registrate.

PHP Reflection API

Verifica il meccanismo che legge i metadati di attributi e parametri.

Fonti primarie consultate

Ultima modifica il 31 ottobre 2026