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

# Container-Tags und Plugin-Aggregation in Paketen

> Zeigt, wie Sie mit Container-Tags in Laravel 13 Implementierungen aus mehreren Paketen zusammenführen, und untersucht die verzögerte Auflösung von tagged(), den Unterschied zu giveTagged(), doppelte Registrierungen und Lebenszyklen.

Wenn mehrere Pakete oder die nutzende Anwendung einem gemeinsamen aggregierenden Service Funktionen hinzufügen sollen, können Sie `tag()` und `tagged()` des Containers verwenden. Die aggregierende Seite muss dabei nicht alle Implementierungsklassen kennen.

Diese Seite bezieht sich auf Laravel 13 und basiert auf der Implementierung und den offiziellen Tests von Framework `v13.38.0`. Die Grundlagen von Bindings finden Sie unter [Service-Container](https://kawax.biz/de/service-container.md), die Auslieferung von Providern unter [Paketentwicklung](https://kawax.biz/de/advanced/package-development.md).

## Tags bündeln die Registrierungsschlüssel von Services

`tag()` fügt einen Registrierungsschlüssel des Containers, etwa einen Klassennamen, der Liste eines Tags hinzu. Das bloße Taggen erzeugt den Service noch nicht.

`tagged()` gibt ein `iterable` zurück, mit dem die unter diesem Tag registrierten Services durchlaufen werden. Es handelt sich nicht um eine automatische Suche nach Klassen, die ein bestimmtes Interface implementieren, sondern um einen Mechanismus, an dem jeder Provider ausdrücklich teilnimmt. Mit den Tags für veröffentlichbare Ressourcen bei `vendor:publish --tag` hat das ebenfalls nichts zu tun.

```mermaid theme={null}
flowchart TD
    A["register() jedes Providers"] --> B["Implementierung mit bind() binden<br>und demselben Tag hinzufügen"]
    B --> C["Aggregierenden Service auflösen"]
    C --> D["iterable von tagged() halten"]
    D --> E["Mit foreach durchlaufen"]
    E --> F["make() pro Registrierungsschlüssel"]
    F --> G["collect() der Implementierung aufrufen"]
```

## Ein erweiterbares Report-Paket erstellen

### Einen gemeinsamen Contract für die Implementierungen bereitstellen

Als Beispiel erstellen wir das Paket `acme/reports`, das mehrere Reports sammelt. Vorausgesetzt wird eine PSR-4-Konfiguration in Composer, die `Acme\Reports\` auf `src/` abbildet.

Definieren Sie in `src/Contracts/Report.php` den Contract, den Plugins implementieren.

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

namespace Acme\Reports\Contracts;

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

Als minimale Implementierungen legen Sie `src/RuntimeReport.php` und `src/MemoryReport.php` an.

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

### Der aggregierende Service erhält ein iterable

`src/ReportCollector.php` ruft beim Durchlaufen der einzelnen Implementierungen die Sammellogik auf. Wenn Sie den öffentlichen Tag-Namen als Konstante definieren, kann auch die nutzende Seite auf denselben Namen verweisen.

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

Mit dem PHP-Typ `iterable` allein lässt sich der Typ der Elemente nicht prüfen, und auch `tag()` selbst kontrolliert nicht, ob es sich um eine Implementierung von `Report` handelt. In diesem Beispiel prüft deshalb auch die aggregierende Seite den Contract.

### In register() nur Bindings und Tags registrieren

Registrieren Sie in `src/ReportsServiceProvider.php` die Standardimplementierungen und den aggregierenden Service. Die Factory des aggregierenden Service gibt das erhaltene `iterable` unverändert weiter.

```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 Pakete oder die nutzende Anwendung stellen etwa einen `DailyReport` bereit, der `Report` implementiert, und fügen ihn in `register()` ihres eigenen Providers demselben Tag hinzu.

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

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

Rufen Sie nach der Registrierung aller Provider `app(ReportCollector::class)->collect()` auf, werden auch die hinzugefügten Implementierungen einbezogen. Auch wenn weitere Implementierungen hinzukommen, müssen Sie die Erzeugung von `ReportCollector` nicht ändern.

## tagged() und giveTagged() unterscheiden sich im Erzeugungszeitpunkt

In der untersuchten Implementierung gibt `tagged()` für einen registrierten Tag einen `RewindableGenerator` zurück. `make()` wird für jeden Service erst beim Durchlaufen aufgerufen; brechen Sie das Durchlaufen vorzeitig ab, werden die übrigen Services nicht erzeugt.

Außerdem erzeugt `RewindableGenerator` bei jedem Durchlauf einen neuen Generator. Er cached die Ergebnisse der Services nicht; bei einem gewöhnlichen `bind()` werden die Instanzen beim erneuten Durchlaufen ebenfalls neu erzeugt. Services mit `singleton()` oder `scoped()` folgen ihren jeweiligen Regeln für die gemeinsame Nutzung.

Das kontextuelle Binding `giveTagged()` wandelt dagegen das Ergebnis von `tagged()` bei der Auflösung mit `iterator_to_array()` in ein Array um.

| Injektionsweg | Zeitpunkt der Auflösung der einzelnen Services | Erneutes Durchlaufen im selben aggregierenden Objekt |
| - | - | - |
| Ergebnis von `tagged()` unverändert übergeben | Beim Durchlaufen des jeweiligen Elements | Erneutes `make()` |
| `iterable`-Argument mit `#[Tag(...)]` | Beim Durchlaufen des jeweiligen Elements | Erneutes `make()` |
| Injektion mit `giveTagged()` | Bei der Auflösung der Abhängigkeiten des aggregierenden Service | Elemente des gehaltenen Arrays werden wiederverwendet |

Möchten Sie die Abhängigkeiten auf einmal als Array auflösen, können Sie das obige `bind()` mit Factory durch die folgende Registrierung ersetzen.

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

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

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

Auch wenn `$reports` den Typ `iterable` hat, ist der auf diese Weise gehaltene Wert ein Array. Der Typ des Arguments allein garantiert keine verzögerte Auflösung. Zudem löst `giveTagged()` nur die Instanzen auf und ruft nicht `collect()` der einzelnen Implementierungen auf.

`resolve()` des Attributs `Tag` gibt `tagged()` unverändert zurück. Wenn Sie das Attribut verwenden, geben Sie es wie unter [Kontextuelle Attribute](https://kawax.biz/de/service-container.md#kontextuelle-attribute) an einem Konstruktorargument an und überlassen die Erzeugung der Klasse dem Container. Bei einem `new` innerhalb einer eigenen Factory wird das Attribut nicht automatisch angewendet.

## Den Registrierungsvertrag für Plugins nicht brechen

### Doppelte Registrierungen und Reihenfolge explizit regeln

`tag()` fügt der Liste lediglich Einträge hinzu und entfernt keine Duplikate, wenn derselbe Registrierungsschlüssel erneut hinzugefügt wird. Wird dieselbe Implementierung zweimal durchlaufen, wird `collect()` zweimal aufgerufen, selbst wenn ein Singleton dasselbe Objekt zurückgibt.

Bündeln Sie die Tag-Registrierung an einer Stelle im Provider, der für die jeweilige Implementierung zuständig ist. `bindIf()` ist eine API, die das Überschreiben von Bindings vermeidet, und verhindert keine doppelten Aufrufe von `tag()` danach.

Die Reihenfolge beim Durchlaufen entspricht der Reihenfolge, in der die Einträge der Tag-Liste hinzugefügt wurden. Da Tags keine Sortierung nach Priorität kennen, sollten Sie die Verarbeitungsreihenfolge zwischen verschiedenen Paketen nicht allein von der Ladereihenfolge der Provider abhängig machen. Ist eine Reihenfolge als Teil der öffentlichen Spezifikation erforderlich, nehmen Sie Priorität oder Kennung in den Contract auf und sortieren auf der aggregierenden Seite explizit.

### Provider, die Tags hinzufügen, zuerst registrieren

`tagged()` für einen nicht registrierten Tag wirft keine Exception, sondern gibt ein leeres Array zurück. Spätere Tag-Registrierungen wirken sich weder auf ein bereits erhaltenes leeres Array noch auf ein von `giveTagged()` bereits erzeugtes Array aus.

Auch beim verzögerten Durchlaufen registrierter Tags sollten Sie nicht von Änderungen der Liste während der Ausführung ausgehen. Das untersuchte `tagged()` übergibt die Anzahl zum Zeitpunkt des Abrufs, verweist beim Durchlaufen aber auf die Tag-Liste. Wenn Sie die Tags erst nach Abschluss der Registrierung aller Provider verwenden, vermeiden Sie auch Abweichungen zwischen Anzahl und durchlaufenen Elementen.

> [!WARNING]
>
> Das Durchlaufen eines Tags sucht nicht anhand des Tag-Namens nach noch nicht geladenen Providern. Ist der Provider, der den Tag hinzufügt, ein `DeferrableProvider`, wird die Tag-Registrierung erst ausgeführt, wenn dessen Service auf anderem Weg aufgelöst wird, und die Implementierung kann in der Liste fehlen. Verwenden Sie für die Tag-Registrierung von Implementierungen, die immer teilnehmen sollen, einen gewöhnlichen Provider, und erzeugen Sie bei der Registrierung keine Implementierungen.

Die Bedingungen für den Start verzögerter Provider finden Sie unter [DeferrableProvider](https://kawax.biz/de/advanced/deferred-provider.md), das Laden bei der Installation unter [Automatische Paketerkennung](https://kawax.biz/de/advanced/package-discovery.md).

### Tags und Lebenszyklus von Services trennen

Durch das Taggen wird ein Service nicht automatisch zu einem Singleton oder scoped. Dieses Beispiel verwendet ein gewöhnliches `bind()` und speichert keine Sammelergebnisse im aggregierenden Objekt.

Insbesondere wenn Sie ein mit `giveTagged()` aufgelöstes Array in einem aggregierenden Singleton-Service halten, können auch die scoped Services in diesem Array erhalten bleiben. Das Zurücksetzen eines Scopes ersetzt keine Referenzen, die bereits in einem anderen Objekt gespeichert sind. Für Entwürfe, die in Octane oder Queue-Workern eingesetzt werden, siehe auch [Lebenszyklus-Design für Paket-Services](https://kawax.biz/de/advanced/package-service-lifetimes.md).

## Verzögerte Auflösung und Umwandlung in ein Array testen

Mit dem Composer-Autoloading des Pakets und einer PHPUnit-Umgebung können Sie den folgenden Test in `tests/ReportCollectorTest.php` hinzufügen. Er ruft die Registrierung des Providers mit einem eigenständigen Container auf und prüft die Anzahl der Erzeugungen. Tests, die auch die Integration in eine Anwendung und die automatische Erkennung umfassen, ergänzen Sie mit [Orchestra Testbench](https://kawax.biz/de/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;
    }
}
```

Prüfen Sie bei Paket-Updates zusätzlich die folgenden Kombinationen.

| Fall | Was zu prüfen ist |
| - | - |
| Zusätzliche Implementierung beim selben Tag registriert | Sie wird in die Aggregation aufgenommen und besteht die Typprüfung des Contracts |
| Dieselbe Implementierung doppelt registriert | Die doppelte Ausführung der Sammellogik lässt sich erkennen |
| Tag nicht registriert oder falscher Tag-Name | Es ist klar, ob eine leere Liste zulässig ist oder das Paket einen Fehler auslöst |
| Wiederverwendung in einem anderen Request oder Job | Scoped Services und Sammelergebnisse werden nicht übernommen |
| Tag-Name oder Contract geändert | Die Kompatibilität wird als öffentliche API bewertet, auf die die Provider der nutzenden Seite verweisen |

## Herangezogene Primärquellen

* [Offizielle Laravel-Dokumentation: Tagging](https://github.com/laravel/docs/blob/13.x/container.md#tagging)
* [Offizielle Laravel-Dokumentation: Binding primitives・Variadic tag dependencies](https://github.com/laravel/docs/blob/13.x/container.md#binding-primitives)
* [Offizielle Laravel-Dokumentation: Service Provider](https://github.com/laravel/docs/blob/13.x/providers.md)
* [Offizielle Laravel-Dokumentation: Paketentwicklung](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: Erneutes Durchlaufen und Anzahl](https://github.com/laravel/framework/blob/v13.38.0/src/Illuminate/Container/RewindableGenerator.php)
* [Tag-Attribut: resolve()](https://github.com/laravel/framework/blob/v13.38.0/src/Illuminate/Container/Attributes/Tag.php)
* [Offizieller Test: ContainerTaggingTest](https://github.com/laravel/framework/blob/v13.38.0/tests/Container/ContainerTaggingTest.php)
* [Offizieller Test: ContextualBindingTest](https://github.com/laravel/framework/blob/v13.38.0/tests/Container/ContextualBindingTest.php)


## Related topics

- [Service-Container](https://kawax.biz/de/service-container.md)
- [Weiterführende Themen](https://kawax.biz/de/advanced/index.md)
- [Event-Listener in Paketen registrieren und cachen](https://kawax.biz/de/advanced/package-events.md)
- [Öffentliche Paket-Assets veröffentlichen und aktualisieren](https://kawax.biz/de/advanced/package-assets.md)
- [Artisan-Befehle in Paketen registrieren und Abhängigkeiten auflösen](https://kawax.biz/de/advanced/package-commands.md)


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