v13.36.0 und verknüpft die grundlegenden Registrierungsmethoden des Service Containers mit der langfristigen Wartung von Paketen. Allein die Wahl von scoped() setzt weder Referenzen zwischen Objekten noch statischen Zustand vollständig zurück.
Die Registrierungsmethode nach der Haltedauer des Service wählen
Diese Tabelle vergleicht die gewöhnliche Auflösung. Sonderwege wie die Auflösung mit expliziten Parametern oder Contextual Binding sollten Sie gesondert prüfen.
Auch bei
bind() bleibt die Referenz bestehen, wenn Sie den abgerufenen Service in einer Eigenschaft eines anderen Singletons speichern. Die Registrierungsmethode ist eine Regel des Containers für Erzeugung und Wiederverwendung, keine Regel, die die vom Nutzer gehaltenen Objekte zwangsweise neu erstellt.
scoped kombiniert eine geteilte Registrierung mit der Erfassung der zu entfernenden Schlüssel
Das untersuchteContainer::scoped() erledigt zwei Dinge:
- Es erfasst den Registrierungsschlüssel in
scopedInstances. - Es ruft
singleton()auf und registriert damit ein geteiltes Binding.
instances gespeichert. forgetScopedInstances() entfernt für die erfassten Schlüssel die gespeicherten Instanzen per unset(). Die Erzeugungslogik des Bindings bleibt erhalten, sodass die nächste gewöhnliche Auflösung ein neues Objekt erstellen kann.
Dieser Vorgang initialisiert weder die Eigenschaften des alten Objekts neu noch schreibt er Referenzen um, die andere Objekte halten. Ein altes Objekt, auf das noch eine Referenz besteht, kann weiterhin verwendet werden.
Die Grenze wird von der Ausführungsumgebung gesetzt
Der Container selbst überwacht nicht den Beginn von HTTP-Requests oder Jobs.- Queue-Worker:
QueueServiceProviderübergibt dem Worker einen Callback, der den Scope zurücksetzt.Worker::daemon()ruft diesen in der Schleife vor dem Abrufen des nächsten Jobs auf. Im Callback werden unter anderemforgetScopedInstances()ausgeführt und die aufgelösten Instanzen der Facades geleert. - Octane: Die offizielle Standardkonfiguration registriert
FlushTemporaryContainerInstancesfürOperationTerminated. Dieser Listener ruftforgetScopedInstances()auf. - Eigene dauerhaft laufende Befehle oder Schleifen: Das bloße Wiederholen von
make()erzeugt keine Grenze. Der Lebenszyklus muss auf Seiten der Ausführungsplattform entworfen werden.
Innerhalb einer Verarbeitung geteilten Zustand als scoped registrieren
Betrachten wir ein Versandpaket, das innerhalb eines Requests oder Jobs die ID eines Lieferziels teilen möchte. Es hält nicht direkt den HTTP-Request, sondern speichert nur den benötigten Wert.handle() eines Jobs der nutzenden Anwendung wird die ID gesetzt, nachdem die Eingabe validiert und die Berechtigung geprüft wurde. Andere Services, die diesen Typ im selben Lebenszyklus auflösen, können auf denselben Context zugreifen.
scoped() keine Persistierung in einer Datenbank oder einem externen Cache. Werte für den nächsten Job übergeben Sie explizit als Daten des Jobs.
In Octane werden
register() und boot() des Service Providers beim ersten Start des Workers ausgeführt. Entwerfen Sie es nicht so, dass der aktuelle Benutzer oder das aktuelle Lieferziel in boot() im Context gespeichert wird. Werte pro Verarbeitung setzen Sie erst, nachdem die Verarbeitung begonnen hat.Referenzen von einem Singleton auf scoped Services werden nicht automatisch aktualisiert
Bei der folgenden Registrierung genügt es nicht, nur den Context als scoped zu registrieren:DeliveryService den Context im Konstruktor in einer Eigenschaft speichert, behält er den zuerst erzeugten Context. Auch wenn der gespeicherte Context aus dem Container entfernt wird, ändert sich die Eigenschaft des bereits erzeugten DeliveryService nicht.
Wählen Sie die Gegenmaßnahme passend zur Verantwortung des Service:
- Die pro Verarbeitung benötigten Werte als Methodenargumente übergeben. Wenn kein Zustand geteilt werden muss, ist dies am einfachsten.
- Auch die haltende Seite als scoped registrieren. Sowohl
DeliveryServiceals auch der Context werden pro Verarbeitung neu erstellt. - Einen Resolver übergeben, der zum Zeitpunkt der Verwendung auflöst. Wenn der Singleton beibehalten werden muss, holen Sie den benötigten Service jedes Mal aus dem aktuellen Container.
DeliveryService in diesem Beispiel muss so umgestellt werden, dass er im Konstruktor eine Closure entgegennimmt und eine API bietet, die sie bei jeder Operation aufruft. Ruft der Konstruktor den Resolver nur einmal auf und speichert das Ergebnis, ist das Problem nicht gelöst. Dies ist ein Designbeispiel, das den Container-Resolver aus der offiziellen Octane-Dokumentation auf einen paketspezifischen Context überträgt.
Auch Zustand prüfen, den scoped nicht entfernt
scoped() entfernt den folgenden Zustand nicht automatisch:
- Werte, die zu paketeigenen statischen Eigenschaften hinzugefügt wurden
- Referenzen auf den Context, die in einem anderen Singleton oder einer globalen Variable gespeichert sind
- „Aktueller Benutzer“ oder „aktuelles Lieferziel“, die im Inneren eines Singletons gehalten werden
- Referenzen, die von eigenen Facades oder Cache-Schichten gehalten werden
Tests schreiben, die im selben Container eine Grenze überschreiten
Wenn HTTP-Tests lediglich jedes Mal eine neue Anwendung erstellen, können Probleme übersehen werden, die entstehen, wenn ein dauerhaft laufender Prozess denselben Container weiterverwendet. Prüfen Sie zunächst mit dem Container allein, dass innerhalb einer Verarbeitung geteilt und nach der Grenze neu erzeugt wird. Die folgenden Tests sind im PHPUnit-Format. Der erste Test verwendet wie die offiziellen Container-TestsforgetScopedInstances() und prüft die Wirkung der Grenze, ohne den gesamten Worker zu starten.
Unit-Tests des Containers allein prüfen nicht den tatsächlichen Ausführungsweg der Octane-Sandbox oder des Queue-Workers. Für jede Ausführungsumgebung, deren Unterstützung Sie zusichern, benötigen Sie zusätzlich Integrationstests mit aufeinanderfolgenden Verarbeitungen. Bei Deployments, die Registrierungsmethoden oder Service-Implementierungen ändern, laden Sie dauerhaft laufende Prozesse neu, damit der neue Code angewendet wird.
Verwandte Seiten
Hooks nach der Service-Auflösung in Paketen
Erläutert afterResolving, den Abruf geteilter Instanzen und Unterschiede bei der Konfiguration nach erneuter Auflösung.
Laravel Octane
Erläutert das Halten von Request und Container sowie Hinweise für den Betrieb dauerhaft laufender Prozesse.
Herangezogene Primärquellen
- Offizielle Laravel-Dokumentation: Binding scoped singletons
- Offizielle Laravel-Dokumentation: Dependency injection and Octane
- Container: scoped(), singleton(), forgetScopedInstances(), resolve()
- ContainerTest: Teilen und Neuerzeugung bei scoped
- QueueServiceProvider: an den Worker übergebenes resetScope
- Worker: Scope-Reset in daemon()
- Octane: FlushTemporaryContainerInstances
- Octane: Standard-Listener für OperationTerminated