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 Paketacme/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.
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.
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 insrc/ReportsServiceProvider.php die Standardimplementierungen und den aggregierenden Service. Die Factory des aggregierenden Service gibt das erhaltene iterable unverändert weiter.
DailyReport bereit, der Report implementiert, und fügen ihn in register() ihres eigenen Providers demselben Tag hinzu.
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 gibttagged() 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.
$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.
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öhnlichesbind() 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 intests/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.
Herangezogene Primärquellen
- Offizielle Laravel-Dokumentation: Tagging
- Offizielle Laravel-Dokumentation: Binding primitives・Variadic tag dependencies
- Offizielle Laravel-Dokumentation: Service Provider
- Offizielle Laravel-Dokumentation: Paketentwicklung
- Container: tag()・tagged()
- ContextualBindingBuilder: giveTagged()
- RewindableGenerator: Erneutes Durchlaufen und Anzahl
- Tag-Attribut: resolve()
- Offizieller Test: ContainerTaggingTest
- Offizieller Test: ContextualBindingTest