> ## Documentation Index
> Fetch the complete documentation index at: https://kawax.biz/llms.txt
> Use this file to discover all available pages before exploring further.

# Tag del container e aggregazione di plugin nei pacchetti

> Come aggregare le implementazioni di più pacchetti con i tag del container di Laravel 13, verificando la risoluzione lazy di tagged(), la differenza con giveTagged(), le registrazioni duplicate e il ciclo di vita.

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](https://kawax.biz/it/service-container.md), per la distribuzione dei provider consulta lo [sviluppo di pacchetti](https://kawax.biz/it/advanced/package-development.md).

## 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`.

```mermaid theme={null}
flowchart TD
    A["register() di ogni provider"] --> B["bind() dell'implementazione e<br>aggiunta allo stesso tag"]
    B --> C["Risoluzione del servizio aggregatore"]
    C --> D["Conservazione dell'iterable di tagged()"]
    D --> E["Enumerazione con foreach"]
    E --> F["make() per ogni chiave di registrazione"]
    F --> G["Chiamata a collect() dell'implementazione"]
```

## 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.

```php theme={null}
<?php

namespace Acme\Reports\Contracts;

interface Report
{
    public function collect(): array;
}
```

Come implementazioni minime, prepara `src/RuntimeReport.php` e `src/MemoryReport.php`.

```php theme={null}
<?php

namespace Acme\Reports;

use Acme\Reports\Contracts\Report;

class RuntimeReport implements Report
{
    public function collect(): array
    {
        return ['php_version' => PHP_VERSION];
    }
}
```

```php theme={null}
<?php

namespace Acme\Reports;

use Acme\Reports\Contracts\Report;

class MemoryReport implements Report
{
    public function collect(): array
    {
        return ['allocated_bytes' => memory_get_usage(true)];
    }
}
```

### 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.

```php theme={null}
<?php

namespace Acme\Reports;

use Acme\Reports\Contracts\Report;
use UnexpectedValueException;

class ReportCollector
{
    public const TAG = 'acme.reports.collectors';

    /** @param iterable<Report> $reports */
    public function __construct(private iterable $reports)
    {
    }

    public function collect(): array
    {
        $results = [];

        foreach ($this->reports as $report) {
            if (! $report instanceof Report) {
                throw new UnexpectedValueException('Tagged services must implement Report.');
            }

            $results[] = $report->collect();
        }

        return $results;
    }
}
```

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.

```php theme={null}
<?php

namespace Acme\Reports;

use Illuminate\Contracts\Container\Container;
use Illuminate\Support\ServiceProvider;

class ReportsServiceProvider extends ServiceProvider
{
    public function register(): void
    {
        $this->app->bind(RuntimeReport::class);
        $this->app->bind(MemoryReport::class);

        $this->app->tag([
            RuntimeReport::class,
            MemoryReport::class,
        ], ReportCollector::TAG);

        $this->app->bind(ReportCollector::class, function (Container $app): ReportCollector {
            return new ReportCollector($app->tagged(ReportCollector::TAG));
        });
    }
}
```

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.

```php theme={null}
use Acme\Reports\ReportCollector;
use App\Reports\DailyReport;

$this->app->bind(DailyReport::class);
$this->app->tag(DailyReport::class, ReportCollector::TAG);
```

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()`.

| Metodo di injection | Quando vengono risolti i singoli servizi | Nuova enumerazione sullo stesso oggetto aggregatore |
| - | - | - |
| Passare direttamente il risultato di `tagged()` | Quando si enumera ciascun elemento | Esegue di nuovo `make()` |
| Argomento `iterable` con `#[Tag(...)]` | Quando si enumera ciascun elemento | Esegue di nuovo `make()` |
| Injection con `giveTagged()` | Quando si risolvono le dipendenze del servizio aggregatore | Riutilizza gli elementi dell'array conservato |

Se vuoi risolvere le dipendenze tutte in una volta come array, puoi sostituire il `bind()` con factory visto prima con la registrazione seguente.

```php theme={null}
use Acme\Reports\ReportCollector;

$this->app->bind(ReportCollector::class);

$this->app->when(ReportCollector::class)
    ->needs('$reports')
    ->giveTagged(ReportCollector::TAG);
```

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](https://kawax.biz/it/service-container.md#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.

> [!WARNING]
>
> 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](https://kawax.biz/it/advanced/deferred-provider.md), il caricamento al momento dell'installazione nella [package discovery](https://kawax.biz/it/advanced/package-discovery.md).

### 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](https://kawax.biz/it/advanced/package-service-lifetimes.md).

## 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](https://kawax.biz/it/advanced/package-testing.md).

```php theme={null}
<?php

namespace Acme\Reports\Tests;

use Acme\Reports\ReportCollector;
use Acme\Reports\ReportsServiceProvider;
use Acme\Reports\RuntimeReport;
use Illuminate\Container\Container;
use PHPUnit\Framework\TestCase;

class ReportCollectorTest extends TestCase
{
    public function test_tagged_reports_are_lazy_and_can_be_resolved_again(): void
    {
        $created = 0;
        $app = $this->makeContainer($created);
        $collector = $app->make(ReportCollector::class);

        $this->assertSame(0, $created);
        $this->assertCount(2, $collector->collect());
        $this->assertSame(1, $created);
        $this->assertCount(2, $collector->collect());
        $this->assertSame(2, $created);
    }

    public function test_give_tagged_resolves_an_array_once(): void
    {
        $created = 0;
        $app = $this->makeContainer($created);
        $app->bind(ReportCollector::class);
        $app->when(ReportCollector::class)
            ->needs('$reports')
            ->giveTagged(ReportCollector::TAG);

        $collector = $app->make(ReportCollector::class);

        $this->assertSame(1, $created);
        $this->assertCount(2, $collector->collect());
        $this->assertCount(2, $collector->collect());
        $this->assertSame(1, $created);
    }

    private function makeContainer(int &$created): Container
    {
        $app = new Container;
        (new ReportsServiceProvider($app))->register();
        $app->bind(RuntimeReport::class, function () use (&$created): RuntimeReport {
            $created++;

            return new RuntimeReport;
        });

        return $app;
    }
}
```

Inoltre, quando aggiorni il pacchetto, verifica le combinazioni seguenti.

| Caso | Cosa verificare |
| - | - |
| Registrazione di un'implementazione aggiuntiva sullo stesso tag | È inclusa nell'aggregazione e supera il controllo del tipo del contratto |
| Registrazione duplicata della stessa implementazione | È possibile rilevare che la raccolta viene eseguita due volte |
| Tag non registrato o nome del tag errato | È chiaro se un elenco vuoto è ammesso o se il pacchetto solleva un errore |
| Riutilizzo in un'altra richiesta o job | Non vengono trascinati servizi scoped o risultati della raccolta |
| Modifica del nome del tag o del contratto | La compatibilità viene valutata come API pubblica a cui fanno riferimento i provider degli utilizzatori |

## Fonti primarie consultate

* [Documentazione ufficiale di Laravel: Tagging](https://github.com/laravel/docs/blob/13.x/container.md#tagging)
* [Documentazione ufficiale di Laravel: Binding primitives・Variadic tag dependencies](https://github.com/laravel/docs/blob/13.x/container.md#binding-primitives)
* [Documentazione ufficiale di Laravel: Service provider](https://github.com/laravel/docs/blob/13.x/providers.md)
* [Documentazione ufficiale di Laravel: Sviluppo di pacchetti](https://github.com/laravel/docs/blob/13.x/packages.md)
* [Container: tag()・tagged()](https://github.com/laravel/framework/blob/v13.38.0/src/Illuminate/Container/Container.php)
* [ContextualBindingBuilder: giveTagged()](https://github.com/laravel/framework/blob/v13.38.0/src/Illuminate/Container/ContextualBindingBuilder.php)
* [RewindableGenerator: nuova enumerazione e conteggio](https://github.com/laravel/framework/blob/v13.38.0/src/Illuminate/Container/RewindableGenerator.php)
* [Attributo Tag: resolve()](https://github.com/laravel/framework/blob/v13.38.0/src/Illuminate/Container/Attributes/Tag.php)
* [Test ufficiale: ContainerTaggingTest](https://github.com/laravel/framework/blob/v13.38.0/tests/Container/ContainerTaggingTest.php)
* [Test ufficiale: ContextualBindingTest](https://github.com/laravel/framework/blob/v13.38.0/tests/Container/ContextualBindingTest.php)


## Related topics

- [Service container](https://kawax.biz/it/service-container.md)
- [Argomenti avanzati](https://kawax.biz/it/advanced/index.md)
- [Hook di risoluzione dei servizi nei pacchetti](https://kawax.biz/it/advanced/package-service-resolution.md)
- [Analisi statica dei pacchetti (PHPStan / Larastan)](https://kawax.biz/it/advanced/package-static-analysis.md)
- [Progettare il ciclo di vita dei servizi di un pacchetto](https://kawax.biz/it/advanced/package-service-lifetimes.md)


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.