Vai al contenuto principale
Se vuoi che più pacchetti o l’applicazione che li utilizza possano aggiungere funzionalità allo stesso servizio aggregatore, puoi usare 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 pacchetto acme/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.
Come implementazioni minime, prepara 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.
Il solo tipo 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

In src/ReportsServiceProvider.php registra le implementazioni standard e il servizio aggregatore. La factory del servizio aggregatore passa così com’è l’iterable ottenuto.
Altri pacchetti o l’applicazione che li utilizza preparano un’implementazione di Report, ad esempio DailyReport, e la aggiungono allo stesso tag nel register() del proprio provider.
Dopo la registrazione di tutti i provider, chiamando 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.
Anche se il tipo di $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.
L’enumerazione di un tag non cerca, a partire dal nome del tag, i provider non ancora caricati. Se il provider che aggiunge il tag è un DeferrableProvider, la registrazione del tag non viene eseguita finché quel servizio non viene risolto per un’altra via, e l’implementazione può mancare dall’elenco. Per le implementazioni che devono partecipare sempre, registra il tag in un provider normale e progetta il codice in modo da non creare le implementazioni durante la registrazione.
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 normale bind() 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 in tests/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.
Inoltre, quando aggiorni il pacchetto, verifica le combinazioni seguenti.

Fonti primarie consultate

Ultima modifica il 26 ottobre 2026