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

# Containertags in packages en het bundelen van plugins

> Bundel implementaties uit meerdere packages met containertags in Laravel 13 en bekijk het lazy resolven van tagged(), het verschil met giveTagged(), dubbele registratie en de levenscyclus.

Wanneer je wilt dat meerdere packages of de gebruikende applicatie functionaliteit kunnen toevoegen aan dezelfde bundelende service, kun je `tag()` en `tagged()` van de container gebruiken. De bundelende kant hoeft dan niet alle implementatieklassen te kennen.

Deze pagina is gericht op Laravel 13 en is gecontroleerd aan de hand van de implementatie en de officiële tests van Framework `v13.38.0`. Zie [Service container](https://kawax.biz/nl/service-container.md) voor de basis van bindings en [Packageontwikkeling](https://kawax.biz/nl/advanced/package-development.md) voor het distribueren van providers.

## Een tag groepeert de registratiesleutels van services

`tag()` voegt containerregistratiesleutels, zoals klassenamen, toe aan de lijst van een tag. Alleen een tag toekennen maakt de service nog niet aan.

`tagged()` geeft een `iterable` terug waarmee je de services opsomt die onder die tag zijn geregistreerd. Het is geen functie die automatisch zoekt naar klassen die een bepaalde interface implementeren, maar een mechanisme waaraan elke provider expliciet deelneemt. Het staat ook los van de tags voor publiceerbare resources van `vendor:publish --tag`.

```mermaid theme={null}
flowchart TD
    A["register() van elke provider"] --> B["Implementatie bind()en<br>en aan dezelfde tag toevoegen"]
    B --> C["Bundelende service resolven"]
    C --> D["iterable van tagged() vasthouden"]
    D --> E["Opsommen met foreach"]
    E --> F["make() per registratiesleutel"]
    F --> G["collect() van de implementatie aanroepen"]
```

## Een uitbreidbaar rapportagepackage bouwen

### Een contract voorbereiden dat de implementaties delen

Als voorbeeld bouwen we het package `acme/reports`, dat meerdere rapporten verzamelt. We gaan uit van een PSR-4-configuratie in Composer waarin `Acme\Reports\` overeenkomt met `src/`.

Definieer in `src/Contracts/Report.php` het contract dat plugins implementeren.

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

namespace Acme\Reports\Contracts;

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

Als minimale implementaties maak je `src/RuntimeReport.php` en `src/MemoryReport.php` aan.

```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)];
    }
}
```

### De bundelende service ontvangt een iterable

`src/ReportCollector.php` roept de verzamellogica aan terwijl het elke implementatie opsomt. Als je de publieke tagnaam in een constante zet, kan ook de gebruikende kant naar dezelfde naam verwijzen.

```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;
    }
}
```

Met alleen het PHP-type `iterable` kun je het type van de elementen niet valideren, en ook `tag()` zelf controleert niet of iets een implementatie van `Report` is. In dit voorbeeld controleert daarom ook de bundelende kant het contract.

### In register() alleen bindings en tags registreren

Registreer in `src/ReportsServiceProvider.php` de standaardimplementaties en de bundelende service. De factory van de bundelende service geeft de opgehaalde `iterable` ongewijzigd door.

```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));
        });
    }
}
```

Andere packages of de gebruikende applicatie maken bijvoorbeeld een `DailyReport` die `Report` implementeert en voegen die in `register()` van hun eigen provider aan dezelfde tag toe.

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

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

Wanneer je na de registratie van alle providers `app(ReportCollector::class)->collect()` aanroept, worden ook de toegevoegde implementaties gebundeld. Ook als er meer deelnemende implementaties bijkomen, hoef je de aanmaaklogica van `ReportCollector` niet te wijzigen.

## tagged() en giveTagged() verschillen in het moment van aanmaken

In de gecontroleerde implementatie geeft `tagged()` voor een geregistreerde tag een `RewindableGenerator` terug. `make()` van elke service wordt pas tijdens het opsommen aangeroepen; als je het opsommen halverwege stopt, worden de overige services niet aangemaakt.

Daarnaast maakt `RewindableGenerator` bij elke opsomming een nieuwe Generator aan. Het is geen functie die de resultaten van services cachet: bij een gewone `bind()` worden de instanties bij opnieuw opsommen ook opnieuw aangemaakt. Services met `singleton()` of `scoped()` volgen hun eigen deelregels.

Contextuele binding met `giveTagged()` daarentegen zet het resultaat van `tagged()` bij het resolven met `iterator_to_array()` om naar een array.

| Injectiemethode | Moment waarop afzonderlijke services worden geresolved | Opnieuw opsommen met hetzelfde bundelende object |
| - | - | - |
| Het resultaat van `tagged()` ongewijzigd doorgeven | Bij het opsommen van elk element | Opnieuw `make()` |
| `iterable`-argument met `#[Tag(...)]` | Bij het opsommen van elk element | Opnieuw `make()` |
| Injecteren met `giveTagged()` | Bij het resolven van de afhankelijkheden van de bundelende service | Elementen van de vastgehouden array worden hergebruikt |

Wil je de afhankelijkheden in één keer als array resolven, dan kun je de eerdere `bind()` met factory vervangen door de volgende registratie.

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

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

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

Ook als het type van `$reports` `iterable` is, is de waarde die op deze manier wordt vastgehouden een array. Het type van het argument alleen garandeert geen lazy resolving. Bovendien resolvet `giveTagged()` alleen de instanties; het roept `collect()` van de afzonderlijke implementaties niet aan.

`resolve()` van het `Tag`-attribuut geeft `tagged()` ongewijzigd terug. Wanneer je het attribuut gebruikt, zet je het zoals bij [Contextuele attributen](https://kawax.biz/nl/service-container.md#contextuele-attributen) op een constructorargument en laat je het opbouwen van de klasse aan de container over. Het attribuut wordt niet automatisch toegepast op een `new` binnen je eigen factory.

## Het registratiecontract van plugins niet breken

### Dubbele registratie en volgorde expliciet maken

`tag()` voegt toe aan een lijst en verwijdert geen duplicaten wanneer je dezelfde registratiesleutel nogmaals toevoegt. Wordt dezelfde implementatie twee keer opgesomd, dan wordt `collect()` twee keer aangeroepen, ook als een singleton hetzelfde object teruggeeft.

Breng de tagregistratie samen op één plek: de provider die verantwoordelijk is voor de betreffende implementatie. `bindIf()` is een API om het overschrijven van bindings te voorkomen en voorkomt geen dubbele `tag()` daarna.

De volgorde van opsommen is de volgorde waarin items aan de taglijst zijn toegevoegd. Tags kennen geen sortering op prioriteit, dus laat de verwerkingsvolgorde tussen verschillende packages niet alleen afhangen van de laadvolgorde van providers. Is de volgorde nodig als publieke specificatie, neem dan een prioriteit of identificatie op in het contract en ontwerp het zo dat de bundelende kant expliciet sorteert.

### De provider die tags toevoegt eerst registreren

`tagged()` voor een niet-geregistreerde tag geeft geen exception maar een lege array terug. Tagregistraties die later worden gedaan, worden niet doorgevoerd in een eerder ontvangen lege array of in een array die al met `giveTagged()` is aangemaakt.

Ga ook bij het lazy opsommen van geregistreerde tags er niet van uit dat de lijst tijdens de uitvoering verandert. De gecontroleerde `tagged()` geeft het aantal door op het moment van ophalen, maar raadpleegt bij het opsommen de taglijst. Als je het zo ontwerpt dat je pas gebruikmaakt nadat alle providers zijn geregistreerd, voorkom je ook een mismatch tussen het aantal en wat er wordt opgesomd.

> [!WARNING]
>
> Het opsommen van een tag is geen proces dat aan de hand van de tagnaam zoekt naar providers die nog niet zijn geladen. Als je de provider die tags toevoegt een `DeferrableProvider` maakt, wordt de tagregistratie pas uitgevoerd wanneer die service via een andere weg wordt geresolved, waardoor implementaties in de lijst kunnen ontbreken. Gebruik voor de tagregistratie van implementaties die altijd moeten deelnemen een gewone provider, en ontwerp het zo dat er bij het registreren geen implementaties worden aangemaakt.

De startvoorwaarden van uitgestelde providers lees je in [DeferrableProvider](https://kawax.biz/nl/advanced/deferred-provider.md), en het laden bij installatie in [Automatische detectie van packages](https://kawax.biz/nl/advanced/package-discovery.md).

### De levenscyclus van tags en services scheiden

Een tag toekennen maakt een service niet automatisch singleton of scoped. Dit voorbeeld gebruikt een gewone `bind()` en slaat geen verzamelresultaten op in het bundelende object.

Vooral wanneer je een met `giveTagged()` geresolvede array vasthoudt in een singleton-bundelservice, kunnen ook scoped services in die array vastgehouden blijven. Het resetten van de scope vervangt geen referenties die al in een ander object zijn opgeslagen. Zie ook [Levensduur van packageservices](https://kawax.biz/nl/advanced/package-service-lifetimes.md) voor ontwerpen die in Octane of queue workers worden gebruikt.

## Lazy resolving en omzetting naar een array testen

In een omgeving met de Composer-autoload van het package en PHPUnit kun je de volgende test toevoegen aan `tests/ReportCollectorTest.php`. Deze roept de registratie van de provider aan op alleen de container en controleert hoe vaak er iets wordt aangemaakt. Tests die ook de integratie in de applicatie en automatische detectie omvatten, vul je aan met [Orchestra Testbench](https://kawax.biz/nl/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;
    }
}
```

Controleer bij het updaten van het package daarnaast de volgende combinaties.

| Geval | Wat je controleert |
| - | - |
| Extra implementatie onder dezelfde tag registreren | Wordt meegenomen in de bundeling en doorstaat de typecontrole van het contract |
| Dezelfde implementatie dubbel registreren | Je kunt detecteren dat de verzamellogica dubbel wordt uitgevoerd |
| Tag niet geregistreerd of verkeerde tagnaam | Het is duidelijk of een lege lijst is toegestaan of dat het package een fout geeft |
| Hergebruik in een ander request of een andere job | Scoped services en verzamelresultaten worden niet meegenomen |
| Tagnaam of contract wijzigen | Compatibiliteit beoordelen als publieke API waarnaar providers aan de gebruikende kant verwijzen |

## Geraadpleegde primaire bronnen

* [Officiële Laravel-documentatie: Tagging](https://github.com/laravel/docs/blob/13.x/container.md#tagging)
* [Officiële Laravel-documentatie: Binding primitives en Variadic tag dependencies](https://github.com/laravel/docs/blob/13.x/container.md#binding-primitives)
* [Officiële Laravel-documentatie: Service providers](https://github.com/laravel/docs/blob/13.x/providers.md)
* [Officiële Laravel-documentatie: Packageontwikkeling](https://github.com/laravel/docs/blob/13.x/packages.md)
* [Container: tag() en 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: opnieuw opsommen en aantal](https://github.com/laravel/framework/blob/v13.38.0/src/Illuminate/Container/RewindableGenerator.php)
* [Tag-attribuut: resolve()](https://github.com/laravel/framework/blob/v13.38.0/src/Illuminate/Container/Attributes/Tag.php)
* [Officiële test: ContainerTaggingTest](https://github.com/laravel/framework/blob/v13.38.0/tests/Container/ContainerTaggingTest.php)
* [Officiële test: ContextualBindingTest](https://github.com/laravel/framework/blob/v13.38.0/tests/Container/ContextualBindingTest.php)


## Related topics

- [Service container](https://kawax.biz/nl/service-container.md)
- [Geavanceerde onderwerpen](https://kawax.biz/nl/advanced/index.md)
- [Middleware van packages registreren en het toepassingsbereik](https://kawax.biz/nl/advanced/package-middleware.md)
- [Assets bundelen met Vite](https://kawax.biz/nl/vite.md)
- [De Copilot CLI bundelen](https://kawax.biz/nl/packages/laravel-copilot-sdk/bundle-cli.md)


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