Zum Hauptinhalt springen
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, die Auslieferung von Providern unter Paketentwicklung.

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.

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.
Als minimale Implementierungen legen Sie src/RuntimeReport.php und src/MemoryReport.php an.

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.
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.
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.
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. 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.
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 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.
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, das Laden bei der Installation unter Automatische Paketerkennung.

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.

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.
Prüfen Sie bei Paket-Updates zusätzlich die folgenden Kombinationen.

Herangezogene Primärquellen

Zuletzt geändert am 26. Oktober 2026