Zum Hauptinhalt springen
Um den Modellen der nutzenden Anwendung aus einem Paket heraus Relationen hinzuzufügen, können Sie mit resolveRelationUsing() registrieren, wie die Relation erzeugt wird. Ohne die Modelldateien direkt zu ändern, lassen sich Abrufen, Eager Loading und Abfragen nach Existenzbedingungen genauso wie bei gewöhnlichen Relationen nutzen. Diese Seite basiert auf der offiziellen Implementierung von Laravel Framework v13.40.0. Die Grundlagen von Relationen finden Sie unter Einführung in Eloquent-Relationen, das Laden von Providern unter Paket-Discovery.

Von gewöhnlichen Definitionen abgrenzen

Die offizielle Dokumentation empfiehlt für die übliche Anwendungsentwicklung gewöhnliche Relationsmethoden und weist darauf hin, dass dynamische Definitionen etwa bei der Paketentwicklung nützlich sein können. Dynamische Relations-Properties sind etwas anderes als die Funktion, Relationsdefinitionen von außerhalb des Modells zu registrieren. Außerdem ist resolveRelationUsing() kein Macro, mit dem sich beliebige Methoden hinzufügen lassen. Gestalten Sie den Callback so, dass er eine Eloquent-Relation-Instanz zurückgibt.

Für das vom Nutzer gewählte Modell registrieren

Als Beispiel fügen wir dem Bestellmodell der nutzenden Anwendung eine hasMany-Relation namens acmeNotes hinzu. Angenommen wird, dass der Paket-Namespace Acme\OrderNotes\ per Composer-PSR-4 auf src/ abgebildet ist und das Paket als gewöhnlicher, nicht verzögerter Service-Provider geladen wird.

Konfiguration und verknüpftes Modell vorbereiten

Setzen Sie den Standardwert in config/acme-order-notes.php auf null. Die bloße Installation registriert dann keine Relation an den Modellen der Anwendung.
src/Models/Note.php auf Paketseite ist das verknüpfte Modell.
Im Beispiel wird angenommen, dass die Bestelltabelle einen ganzzahligen Primärschlüssel id hat und die Tabelle acme_order_notes die Spalten id, order_id, body sowie Zeitstempel besitzt. Legen Sie einen Index auf order_id an. Wenn Sie etwa UUIDs verwenden, müssen auch die Typen der zugehörigen Spalten übereinstimmen.
Die Registrierung einer dynamischen Relation erstellt weder Tabellen noch Fremdschlüssel-Constraints. Sie ist ein von Paket-Migrationen veröffentlichen und aktualisieren getrennter Vorgang. Auch der folgende Provider enthält keine DB-Zugriffe und führt keine Migrationen aus.

Die Erzeugungslogik in boot registrieren

Führen Sie in src/OrderNotesServiceProvider.php die Konfiguration zusammen und registrieren Sie in boot() den Callback am Zielmodell.
In der nutzenden Anwendung wird die Konfiguration veröffentlicht und order_model auf das tatsächlich verwendete konkrete Modell gesetzt, etwa \App\Models\Order::class. Das Laden des Providers wird über Discovery konfiguriert.
Bei hasMany() werden der Fremdschlüssel order_id und der Schlüssel auf Elternseite explizit angegeben. So wird auch dann, wenn die Klasse des Elternmodells Purchase heißt, nicht purchase_id abgeleitet, und die Definition bleibt mit der Tabellendefinition des Pakets konsistent.
Das Zielmodell wird von den Administratoren der Anwendung konfiguriert. Entwerfen Sie das Paket nicht so, dass Klassen- oder Relationsnamen direkt aus HTTP-Requests o. Ä. übernommen und pro Request neu registriert werden. Eine Registrierung am Eloquent-Basismodell Model wirkt sich zudem auf alle davon abgeleiteten Modelle aus.

Wie eine gewöhnliche Relation verwenden

Beim konfigurierten Bestellmodell liefert der Methodenaufruf eine HasMany, der Property-Zugriff die Collection der abgerufenen Ergebnisse.
Bei create() setzt hasMany die Spalte order_id, sodass der Fremdschlüssel nicht in das vom Nutzer übergebene Array eingefügt werden muss. Die umgekehrte Relation wird nicht automatisch hinzugefügt; definieren Sie sie bei Bedarf separat.

Im Callback keine Daten abrufen

Gibt der Callback das Ergebnis von get() oder ein einzelnes Modell zurück, kann dies etwa beim Property-Zugriff nicht als gültige Relation behandelt werden. Trennen Sie den Aufbau der Relation vom Abrufen der Daten. Builder::getRelation() beim Eager Loading baut die Relation mit einer neuen Modellinstanz auf. Vermeiden Sie daher Entwürfe, bei denen der Callback voraussetzt, dass bereits eine abgerufene Bestell-ID vorhanden ist, oder abhängig von dieser ID eine andere Relationsdefinition zurückgibt. Auch eine dynamische Registrierung beseitigt weder das N+1-Problem noch die Einschränkungen für Lazy Loading. Wenn Sie mit mehreren Modellen arbeiten, laden Sie Relationen wie gewohnt mit with() o. Ä.

Bei belongsTo auf die Ableitung des Relationsnamens achten

Wird bei belongsTo() der Relationsname weggelassen, leitet die Methode ihn per Backtrace aus dem Aufrufer ab. Verlassen Sie sich bei einer Definition aus einer Closure nicht auf dieselbe Ableitung wie bei gewöhnlichen Modellmethoden, sondern geben Sie neben Fremdschlüssel und Owner-Schlüssel auch den Relationsnamen als viertes Argument explizit an. Das folgende eigenständige Beispiel setzt customer_id auf Bestellseite und id auf Kundenseite voraus.

Die Regeln für Namenskonflikte und Vererbung verstehen

HasRelationships speichert die Closure in einer statischen Property, mit Klassenname des Modells und Relationsname als Schlüssel. Model::__call(), das Aufrufe nicht existierender Methoden empfängt, sucht den Resolver und führt ihn mit der Zielmodellinstanz aus. Das Diagramm zeigt den Pfad, wenn ein Resolver gefunden wird. Beachten Sie, dass bei Konflikten mit bestehenden Methoden oder Attributen ein anderer Pfad genommen wird. Verwenden Sie eindeutige Relationsnamen wie acmeNotes und prüfen Sie Konflikte mit Methoden und Attributen der Nutzer sowie mit anderen Paketen. Mit method_exists() allein lässt sich eine dynamische Registrierung nicht erkennen. Eloquents isRelation() prüft zwar auch Resolver, garantiert aber nicht, dass kein Konflikt mit Attributen besteht.

Lebensdauer von Registrierungszustand und abgerufenen Ergebnissen trennen

Die Registrierung eines Resolvers und die geladenen Relationsdaten, die jedes Modell hält, sind zwei verschiedene Dinge. Wird der Resolver überschrieben, nachdem $order->acmeNotes bereits geladen wurde, aktualisiert sich das von diesem Modell gehaltene Ergebnis nicht automatisch. Wenn Sie neu abrufen müssen, entfernen Sie das Relationsergebnis und verwenden Sie dann etwa load('acmeNotes').
Registriert wird nicht im Service-Container, sondern in einer statischen Property. Wenn die Closure Requests, authentifizierte Nutzer, Tenants o. Ä. einfängt, können diese in langlebigen Prozessen erhalten bleiben. Beschränken Sie Registrierungen auf stabile Definitionen beim Start und aktualisieren Sie bei Konfigurations- oder Codeänderungen auch Prozesse wie Queue-Worker oder Octane. Siehe auch Lebenszyklus-Design für Paket-Services.
Ebenso sollten Sie nicht davon ausgehen, dass Resolver verschwinden, nur weil Testbench die Anwendung neu erzeugt. Für Tests, die den aktivierten und deaktivierten Zustand vergleichen, verwenden Sie separate Modell-Fixtures oder die Prozessisolation von PHPUnit, um Abhängigkeiten von früheren Registrierungen zu vermeiden.

Sowohl Registrierung als auch SQL testen

Prüfen Sie mit Orchestra Testbench nicht nur die Registrierung durch den Provider, sondern auch die Fremdschlüssel und das tatsächliche Abrufen der Relation. Das folgende Beispiel ist tests/DynamicRelationsTest.php. Sorgen Sie dafür, dass die Paketklassen und der Test-Namespace per Composer geladen werden können, und stellen Sie die PDO- und SQLite-Treiber bereit.
Das Testmodell heißt OrderFixture, der Fremdschlüssel bleibt jedoch order_id statt order_fixture_id. So lässt sich bestätigen, dass keine Abhängigkeit von der Ableitung aus dem Klassennamen besteht. Prüfen Sie in der CI des Pakets zusätzlich folgende Grenzfälle:
  • Mit einem anderen, nicht registrierten Fixture wird keine Relation hinzugefügt, wenn order_model null ist.
  • Vererbung an Kindmodelle und die Priorität, wenn nur am Kind ein gleichnamiger Resolver registriert ist.
  • Bei Konflikten mit bestehenden Methoden oder Attributen ist die beabsichtigte API nutzbar.
  • Der Unterschied zwischen einem Modell, dessen Relation bereits einmal geladen wurde, und einem neuen Modell nach erneuter Registrierung des Resolvers.
  • Spaltentypen, Indizes und Fremdschlüssel-Constraints in den unterstützten Datenbanken sowie vom Nutzer geänderte Primärschlüssel.

Herangezogene Primärquellen

Zuletzt geändert am 4. November 2026