Zum Hauptinhalt springen
Manchmal sollen Nutzer eines Pakets benötigte Werte injizieren können, ohne Konfigurationsschlüssel im Service Provider aufzulisten. Wenn Sie kontextuelle Attribute bereitstellen, lassen sich paketspezifische Konfigurationswerte und Services anhand von Metadaten an einem Parameter auflösen. Diese Seite prüft den offiziellen Quellcode von Laravel Framework v13.39.0 und implementiert ein eigenes Attribut, das die Konfiguration courier liest. Wenn das eingebaute #[Config('courier.timeout')] bereits ausreicht, müssen Sie keine eigenen Attribute hinzufügen. Ziehen Sie sie in Betracht, wenn Ihr Paket eigene Schlüsselkonventionen oder Auflösungsregeln als Teil seiner API anbieten soll.

Die Aufgabe eines Attributs ist es, den injizierten Wert festzulegen

Attribute, die Illuminate\Contracts\Container\ContextualAttribute implementieren, werden vom Laravel-Container beim Auflösen von Parametern gelesen. An die nutzende Klasse wird nicht das Attributobjekt selbst übergeben, sondern der Wert, den der Resolver zurückgibt. Normale Bindings und die eingebauten Attribute finden Sie unter Service-Container. Kontextuelle Attribute sind ein anderer Mechanismus als Attribute wie Bind, Singleton und Scoped, die Bindings für Klassen oder Interfaces deklarieren.

Ein Attribut zum Lesen der Paketkonfiguration erstellen

Standardkonfiguration registrieren

Legen Sie zunächst in config/courier.php des Pakets die Standardwerte an.
Führen Sie die Konfiguration in register() von src/CourierServiceProvider.php zusammen. Die automatische Registrierung des Providers ist unter Interne Struktur der automatischen Paket-Erkennung beschrieben, das Veröffentlichen und den Umgang mit dem Konfigurations-Cache finden Sie unter Paketkonfiguration zusammenführen und cachen.

ContextualAttribute implementieren

Definieren Sie in src/Attributes/CourierConfig.php ein Attribut, das dem Konfigurationsschlüssel courier. voranstellt.
In der geprüften Implementierung erhält der Resolver die folgenden drei Argumente.
  1. Die Instanz des Attributs, das am Parameter angebracht ist.
  2. Den Container, der die Abhängigkeit auflöst.
  3. Ein ReflectionParameter, der das Auflösungsziel darstellt.
In diesem Beispiel wird der Parametername verwendet, wenn der Schlüssel weggelassen wird. #[CourierConfig(default: 30)] int $timeout verweist auf courier.timeout. Wenn dieselbe Konfiguration auch nach einer Umbenennung des Parameters auf Nutzerseite gelesen werden soll, geben Sie den Schlüssel explizit an, etwa #[CourierConfig('timeout', default: 30)]. ContextualAttribute ist zum Zeitpunkt der Prüfung ein Marker-Interface ohne deklarierte Methoden. Daher garantiert implements allein weder die Existenz noch die Signatur eines Resolvers. Wenn Sie wie oben resolve() bereitstellen, müssen Sie für dieses Attribut kein normales bind() registrieren.
Verwenden Sie den Konstruktor des Attributs nur zum Speichern von Metadaten. Der Container ruft newInstance() bei der Auflösung und bei der Nachbearbeitung des Attributs getrennt auf, daher ist er kein Ort für eine „einmalige Initialisierung“. Führen Sie dort keine externe Kommunikation aus und halten Sie keinen veränderlichen Zustand.

Die nutzende Klasse über den Container erzeugen

Versehen Sie src/CourierOptions.php mit den Attributen.
Wenn die nutzende Anwendung die Konfiguration überschreibt, wird bei der nächsten Auflösung dieser Wert injiziert.
In diesem Beispiel ist $options->timeout gleich 45 und $options->retries gleich 3. Das Attribut liest nur die Konfiguration und führt keine fachliche Validierung durch, etwa ob es sich um eine positive Ganzzahl handelt oder ob ein zulässiger Höchstwert eingehalten wird. Auch Typdeklarationen ersetzen keine Validierung von Konfigurationswerten.

Die Auflösungsreihenfolge im Konstruktor nachvollziehen

Container::resolveDependencies() bestimmt den Wert jedes Parameters in der folgenden Reihenfolge.
  1. Einen Überschreibungswert verwenden, der z. B. über makeWith() übergeben wurde und zum Parameternamen passt.
  2. Mit Util::getContextualAttributeFromDependency() nach einem kontextuellen Attribut suchen und es auflösen.
  3. Wenn das Ergebnis des Attributs null ist, zur normalen Auflösung von primitiven Werten oder Klassen übergehen.
  4. Nach der Nachbearbeitung des Attributs den ermittelten Wert an den Konstruktor übergeben.
Der Überschreibungsweg aus Schritt 1 überspringt die Attributauflösung und die Nachbearbeitung für diesen Parameter. Die Schlüssel von makeWith() sind keine Konfigurationsschlüssel, sondern Parameternamen der nutzenden Klasse.
In diesem Fall ist timeout unabhängig vom Konfigurationswert 5. Gibt das Attribut dagegen einen Wert ungleich null zurück, hat das Ergebnis des Attributs Vorrang vor einem normalen when()->needs('$timeout')->give(...). Testen Sie daher auch, ob das Hinzufügen eines Attributs im Paket bestehende kontextuelle Bindings der Nutzer verändern kann. Util ermittelt per Reflection die Attribute, die ContextualAttribute implementieren, und wählt das erste davon für die Auflösung aus. Wenn Sie mehrere verschiedene kontextuelle Attribute nebeneinander angeben, werden die Resolver nicht nacheinander kombiniert.

Registrierte Handler haben Vorrang vor resolve()

Container::resolveFromAttribute() sucht zuerst mit dem Klassennamen des Attributs als Schlüssel nach einem Handler, der mit whenHasAttribute() registriert wurde. Gibt es keinen, wird resolve() des Attributs verwendet. Fehlt beides, wird eine BindingResolutionException geworfen. whenHasAttribute() ist keine Einstellung pro nutzender Klasse, sondern pro Attributtyp. Eine erneute Registrierung für dieselbe Attributklasse überschreibt den vorherigen Handler. Wenn ein Paket die Handler anderer Pakete oder der eingebauten Laravel-Attribute bedingungslos ändert, wirkt sich das auf die gesamte nutzende Anwendung aus. Legen Sie das Standardverhalten eigener Attribute in resolve() ab und behandeln Sie Änderungen über Handler als expliziten Erweiterungspunkt.

make(), call() und direkte Aufrufe verhalten sich nicht gleich

Auch app()->call() löst Attribute auf, aber der normale BoundMethod-Pfad fügt das Ergebnis eines gefundenen Attributs unverändert den Argumenten hinzu. Anders als bei der Konstruktorauflösung wird bei einem Ergebnis von null nicht auf die Auflösung des Standardwerts zurückgegriffen. Ein Beispiel, das ohne weitere kontextuelle Bindings den nicht existierenden Schlüssel courier.missing liest:
Auch new CourierOptions(timeout: 5, retries: 1) ist eine normale Instanziierung durch PHP. Das bloße Anbringen eines Attributs fügt direkten new-Aufrufen oder Methodenaufrufen keine Dependency Injection hinzu.
Wenn Konstruktoren und Callables dasselbe Standardverhalten benötigen, verlassen Sie sich nicht nur auf den Standardwert des Parameters, sondern definieren Sie einen Standardwert im Attribut, etwa #[CourierConfig('timeout', default: 30)]. Dieses default greift, wenn der Konfigurationsschlüssel nicht existiert. Es ersetzt keinen vorhandenen Schlüssel, dessen Wert null ist. Bei APIs, in denen null selbst ein bedeutungsvoller Wert ist, prüfen Sie beide Auflösungswege getrennt.

Erneute Injektion von Werten und Lebensdauer von Services trennen

Das Attribut liest die Konfiguration zum Zeitpunkt der Parameterauflösung. Es ist keine Funktion, die Eigenschaften eines bereits erzeugten CourierOptions überwacht oder aktualisiert. Wenn Sie CourierOptions beispielsweise als Singleton registrieren, behält diese Instanz die zuerst injizierten Werte. Auch wenn Sie danach die Konfiguration ändern und ein normales make() aufrufen, ändern sich die Werte derselben Instanz nicht. Auch bei Attributen, die anfragebezogene Informationen lesen, bestimmt das Attribut selbst nicht die Lebensdauer als Singleton oder Scoped. Wählen Sie die Lebensdauer der Services, die injizierte Werte oder Objekte halten, gemäß Lebenszyklus-Design für Paket-Services. Betrachten Sie das Neuerzeugen des Konfigurations-Caches getrennt von der Aktualisierung langlebiger Prozesse und bereits erzeugter Services.

Auflösungswege testen

Tests für das Attribut allein lassen sich wie in den offiziellen Tests mit einem neuen Container und Illuminate\Config\Repository schreiben. Das folgende PHPUnit-Beispiel setzt eine Testumgebung voraus, in der die oben definierten Klassen CourierConfig, CourierOptions und NullableTimeoutOptions geladen sind.
Das Beispiel mit einem Handler, der immer 7 zurückgibt, dient nur zum Testen der Priorität. Innerhalb dieses Containers wirkt es sich auf jede Auflösung desselben Attributs aus, einschließlich retries. Dieser Unit-Test allein deckt die Integration mit der Provider-Registrierung des Pakets, mergeConfigFrom() und dem Konfigurations-Cache nicht ab. Prüfen Sie zusätzlich in der Anwendungsumgebung aus Laravel-Pakete mit Orchestra Testbench testen die Zustände vor der Veröffentlichung, nach der Veröffentlichung und mit Konfigurations-Cache.

Prüfpunkte vor dem Release

  • Behandeln Sie den Klassennamen des Attributs, die Namen der Konstruktorparameter, die Konfigurationsschlüssel und den Rückgabetyp als öffentliche API. Achten Sie auch auf Änderungen an key und default, die als benannte Argumente verwendet werden.
  • Wenn bei fehlendem Schlüssel der Parametername verwendet wird, prüfen Sie, ob eine Umbenennung des Parameters in der nutzenden Klasse die referenzierte Konfiguration verändert.
  • Testen Sie bestehende Bindings ohne Attribut, die Auflösung mit Attribut und das Überschreiben per Parametername jeweils separat.
  • Testen Sie null, fehlende Konfiguration und ungeeignete Typen sowohl mit make() als auch mit call().
  • Stellen Sie sicher, dass Singletons und langlebige Worker keine veralteten Werte halten.
  • Prüfen Sie bei einer Aktualisierung der unterstützten Laravel-Version die Argumente des Resolvers, die Priorität und die Nachbearbeitung der Attribute erneut anhand des offiziellen Quellcodes und der Tests.

Verwandte Seiten

Paketkonfiguration zusammenführen und cachen

Prüfen Sie die Grenzen zwischen Standardkonfiguration, veröffentlichter Konfiguration und Cache-Aktualisierung.

Lebenszyklus-Design für Paket-Services

Wählen Sie die Lebensdauer von Services, die injizierte Werte halten.

Container-Tags und Plugin-Aggregation in Paketen

Lernen Sie das eingebaute Tag-Attribut und die Aggregation registrierter Implementierungen kennen.

PHP Reflection API

Verstehen Sie, wie Metadaten von Attributen und Parametern gelesen werden.

Herangezogene Primärquellen

Zuletzt geändert am 31. Oktober 2026