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 implementanoIlluminate\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 inconfig/courier.php del pacchetto.
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.
- L’istanza dell’attributo applicato al parametro.
- Il container che sta risolvendo la dipendenza.
- Il
ReflectionParameterche rappresenta il parametro da risolvere.
#[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.
Creare la classe utente dal container
Applica l’attributo insrc/CourierOptions.php.
$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.
- Usa il valore di override passato tramite
makeWith()o simili, se corrisponde al nome del parametro. - Cerca e risolve un attributo contestuale con
Util::getContextualAttributeFromDependency(). - Se il risultato dell’attributo è
null, passa alla normale risoluzione di valori primitivi o classi. - Dopo la post-elaborazione dell’attributo, passa il valore determinato al costruttore.
makeWith() sono i nomi dei parametri della classe utente, non le chiavi di configurazione.
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 unCourierOptions 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 nuovoContainer e Illuminate\Config\Repository. Il seguente esempio PHPUnit presuppone un ambiente di test in cui siano caricati CourierConfig, CourierOptions e NullableTimeoutOptions definiti sopra.
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
keyedefault, 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 conmake()sia concall(). - 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
- Documentazione ufficiale di Laravel: Defining custom attributes
- Documentazione ufficiale di Laravel: configurazione dei pacchetti
- Container: resolveDependencies(), resolveFromAttribute(), whenHasAttribute() e post-elaborazione degli attributi
- BoundMethod: risoluzione degli argomenti delle callable
- Util: rilevamento dei ContextualAttribute tramite Reflection
- ContextualAttribute: interfaccia marcatore
- Config: attributo di configurazione integrato
- Config Repository: get() e lettura dei valori di configurazione
- Arr: valore predefinito di get() e gestione dei null esistenti
- Test ufficiali: resolver degli attributi, handler registrati, ReflectionParameter e risoluzione con call