tag() e tagged() del container. Il lato che aggrega non ha bisogno di conoscere tutte le classi di implementazione.
Questa pagina si riferisce a Laravel 13 ed è stata verificata sull’implementazione e sui test ufficiali di Framework v13.38.0. Per le basi dei binding consulta il service container, per la distribuzione dei provider consulta lo sviluppo di pacchetti.
I tag raggruppano le chiavi di registrazione dei servizi
tag() aggiunge all’elenco di un tag le chiavi di registrazione del container, come i nomi delle classi. Applicare un tag non crea il servizio.
tagged() restituisce un iterable per enumerare i servizi registrati con quel tag. Non è una funzionalità che cerca automaticamente le classi che implementano una determinata interfaccia, ma un meccanismo a cui ogni provider partecipa in modo esplicito. È anche distinto dai tag delle risorse pubblicabili di vendor:publish --tag.
Creare un pacchetto di report estensibile
Preparare il contratto condiviso dalle implementazioni
Come esempio, creiamo il pacchettoacme/reports, che raccoglie più report. Si presuppone una configurazione PSR-4 di Composer in cui Acme\Reports\ corrisponde a src/.
In src/Contracts/Report.php definisci il contratto che i plugin implementano.
src/RuntimeReport.php e src/MemoryReport.php.
Il servizio aggregatore riceve un iterable
src/ReportCollector.php invoca la raccolta mentre enumera ciascuna implementazione. Se definisci come costante il nome del tag pubblico, anche chi usa il pacchetto può fare riferimento allo stesso nome.
iterable di PHP non permette di validare il tipo degli elementi, e nemmeno tag() verifica che si tratti di un’implementazione di Report. In questo esempio il contratto viene controllato anche dal lato che aggrega.
In register() registra solo binding e tag
Insrc/ReportsServiceProvider.php registra le implementazioni standard e il servizio aggregatore. La factory del servizio aggregatore passa così com’è l’iterable ottenuto.
Report, ad esempio DailyReport, e la aggiungono allo stesso tag nel register() del proprio provider.
app(ReportCollector::class)->collect() vengono aggregate anche le implementazioni aggiunte. Anche se le implementazioni partecipanti aumentano, non serve modificare la logica di creazione di ReportCollector.
tagged() e giveTagged() creano i servizi in momenti diversi
Nell’implementazione esaminata,tagged() su un tag registrato restituisce un RewindableGenerator. Il make() di ciascun servizio viene chiamato durante l’enumerazione e, se interrompi l’enumerazione a metà, i servizi restanti non vengono creati.
Inoltre, RewindableGenerator crea un nuovo Generator a ogni enumerazione. Non mette in cache i risultati dei servizi: con un normale bind(), una nuova enumerazione ricrea anche le istanze. I servizi singleton() o scoped() seguono le rispettive regole di condivisione.
Il giveTagged() del contextual binding, invece, al momento della risoluzione converte il risultato di tagged() in un array con iterator_to_array().
Se vuoi risolvere le dipendenze tutte in una volta come array, puoi sostituire il
bind() con factory visto prima con la registrazione seguente.
$reports è iterable, con questo metodo il valore conservato è un array. Il solo tipo dell’argomento non garantisce la risoluzione lazy. Inoltre, giveTagged() si occupa di risolvere le istanze, non di chiamare il collect() di ciascuna implementazione.
Il resolve() dell’attributo Tag restituisce direttamente tagged(). Se usi l’attributo, specificalo su un argomento del costruttore come negli attributi contestuali e lascia al container la costruzione della classe. L’attributo non viene applicato automaticamente a un new all’interno di una factory personalizzata.
Non rompere il contratto di registrazione dei plugin
Rendere espliciti duplicati e ordine
tag() aggiunge elementi a un elenco e, se aggiungi di nuovo la stessa chiave di registrazione, non rimuove il duplicato. Se la stessa implementazione viene enumerata due volte, collect() viene chiamato due volte anche se un singleton restituisce lo stesso oggetto.
Concentra la registrazione del tag in un unico punto, nel provider responsabile di ciascuna implementazione. bindIf() è un’API per evitare di sovrascrivere un binding, non impedisce i duplicati di un tag() successivo.
L’ordine di enumerazione è quello di aggiunta all’elenco del tag. I tag non prevedono alcun ordinamento per priorità, quindi non far dipendere l’ordine di elaborazione tra pacchetti diversi solo dall’ordine di caricamento dei provider. Se l’ordine fa parte della specifica pubblica, includi una priorità o un identificatore nel contratto e progetta il lato aggregatore in modo che ordini esplicitamente.
Registrare prima i provider che aggiungono i tag
tagged() su un tag non registrato restituisce un array vuoto, non un’eccezione. Le registrazioni di tag successive non si riflettono né su un array vuoto già ricevuto né su un array già creato con giveTagged().
Anche per l’enumerazione lazy di un tag registrato, non dare per scontato che l’elenco possa cambiare durante l’esecuzione. Il tagged() esaminato passa il conteggio al momento del recupero, mentre durante l’enumerazione consulta l’elenco del tag. Progettando il codice in modo da usarlo solo dopo la registrazione di tutti i provider, eviti anche discrepanze tra il conteggio e gli elementi enumerati.
Le condizioni di avvio dei provider differiti sono descritte in DeferrableProvider, il caricamento al momento dell’installazione nella package discovery.
Separare il ciclo di vita dei tag e dei servizi
Applicare un tag non rende automaticamente un servizio singleton o scoped. Questo esempio usa un normalebind() e non salva i risultati della raccolta nell’oggetto aggregatore.
In particolare, se conservi in un servizio aggregatore singleton l’array risolto con giveTagged(), anche i servizi scoped contenuti nell’array possono rimanere conservati. Il reset dello scope non sostituisce i riferimenti già salvati in altri oggetti. Per progettazioni destinate a Octane o ai queue worker consulta anche Progettare il ciclo di vita dei servizi di un pacchetto.
Testare la risoluzione lazy e la conversione in array
Con l’autoload di Composer del pacchetto e un ambiente PHPUnit, puoi aggiungere il test seguente intests/ReportCollectorTest.php. Chiama la registrazione del provider sul solo container e verifica il numero di creazioni. I test che includono l’integrazione con l’applicazione e la discovery automatica si completano con Orchestra Testbench.
Fonti primarie consultate
- Documentazione ufficiale di Laravel: Tagging
- Documentazione ufficiale di Laravel: Binding primitives・Variadic tag dependencies
- Documentazione ufficiale di Laravel: Service provider
- Documentazione ufficiale di Laravel: Sviluppo di pacchetti
- Container: tag()・tagged()
- ContextualBindingBuilder: giveTagged()
- RewindableGenerator: nuova enumerazione e conteggio
- Attributo Tag: resolve()
- Test ufficiale: ContainerTaggingTest
- Test ufficiale: ContextualBindingTest