resolveRelationUsing() registreren hoe de relatie wordt aangemaakt. Zonder het modelbestand rechtstreeks te wijzigen, kun je net als bij gewone relaties ophalen, eager loaden en zoeken op het bestaan van relaties.
Deze pagina is gecontroleerd aan de hand van de officiële broncode van Laravel Framework v13.40.0. Zie Inleiding tot Eloquent-relaties voor de basis van relaties en Package discovery voor het laden van providers.
Onderscheid met gewone definities
De officiële documentatie raadt voor gewone applicatieontwikkeling normale relatiemethoden aan en geeft aan dat dynamische definities nuttig kunnen zijn bij bijvoorbeeld packageontwikkeling.
Dynamische relatieproperties zijn iets anders dan de functie om relatiedefinities van buiten het model te registreren. Bovendien is
resolveRelationUsing() geen macro die willekeurige methoden toevoegt. Ontwerp de callback zo dat hij een Eloquent Relation-instantie retourneert.
Registreren op het model dat de gebruiker kiest
Als voorbeeld voegen we eenhasMany met de naam acmeNotes toe aan het ordermodel van de gebruikende applicatie. We gaan uit van een opzet waarin de namespace Acme\OrderNotes\ van de package via Composer-PSR-4 aan src/ is gekoppeld en als gewone, niet-uitgestelde service provider wordt geladen.
Configuratie en gerelateerd model voorbereiden
Zet de standaardwaarde inconfig/acme-order-notes.php op null. Alleen het installeren registreert dan nog geen relatie op de modellen van de applicatie.
src/Models/Note.php aan de kant van de package is het gerelateerde model.
id heeft en dat de tabel acme_order_notes de kolommen id, order_id, body en timestamps heeft. Voorzie order_id van een index. Gebruik je bijvoorbeeld UUID’s, zorg dan dat ook de types van de gerelateerde kolommen overeenkomen.
Het registreren van een dynamische relatie maakt geen tabellen of foreign key-constraints aan. Dat is een ander proces dan Migraties van packages publiceren en bijwerken. Ook de provider hieronder bevat geen databasetoegang of het uitvoeren van migraties.
De aanmaakmethode registreren in boot
Merge insrc/OrderNotesServiceProvider.php de configuratie en registreer in boot() de callback op het doelmodel.
order_model naar het concrete model dat je daadwerkelijk gebruikt, zoals \App\Models\Order::class. Het laden van de provider stel je in via package discovery.
hasMany() worden de foreign key order_id en de sleutel aan de ouderkant expliciet opgegeven. Zo wordt, ook als de klassenaam van het oudermodel Purchase is, niet purchase_id afgeleid en komt het overeen met de tabeldefinitie van de package.
Gebruiken zoals een gewone relatie
Op het geconfigureerde ordermodel retourneert een methodeaanroep eenHasMany en een property-toegang de collectie met opgehaalde resultaten.
create() stelt hasMany order_id in, dus je hoeft de foreign key niet toe te voegen aan de array die de gebruiker doorgeeft. De omgekeerde relatie wordt niet automatisch toegevoegd; definieer die indien nodig apart.
Voer in de callback geen ophaalactie uit
Als de retourwaarde van de callback het resultaat vanget() of een enkel model is, kan deze bijvoorbeeld bij property-toegang niet als geldige relatie worden behandeld. Scheid het opbouwen van de relatie van het ophalen van data.
Builder::getRelation() bij eager loading bouwt de relatie op met een nieuwe modelinstantie. Vermijd daarom ontwerpen waarin de callback ervan uitgaat dat er al een opgehaald order-ID is, of afhankelijk van dat ID een andere relatiedefinitie retourneert.
Ook met dynamische registratie verdwijnen het N+1-probleem en de beperkingen van lazy loading niet. Laad relaties bij het werken met meerdere modellen zoals gebruikelijk met bijvoorbeeld with().
Let bij belongsTo ook op het afleiden van de relatienaam
Als je de relatienaam weglaat, leidtbelongsTo() de aanroeper af via een backtrace. Bij definities vanuit een closure kun je, in plaats van te vertrouwen op dezelfde afleiding als bij een gewone modelmethode, naast de foreign key en de owner key ook de relatienaam als vierde argument expliciet opgeven.
Hieronder een losstaand voorbeeld waarbij de orderkant customer_id heeft en de klantkant id.
De regels voor naamconflicten en overerving begrijpen
HasRelationships slaat de closure op in een static property met de klassenaam van het model en de relatienaam als sleutel. Model::__call(), dat de aanroep van een niet-bestaande methode ontvangt, zoekt de resolver op en voert die uit met de betreffende modelinstantie.
Het diagram toont het pad wanneer een resolver wordt gevonden. Let op dat conflicten met bestaande methoden of attributen een ander pad volgen.
Gebruik een unieke relatienaam zoals
acmeNotes en controleer op conflicten met methoden en attributen van de gebruiker en met andere packages. Met alleen method_exists() kun je het bestaan van een dynamische registratie niet controleren. Eloquents isRelation() controleert ook resolvers, maar garandeert niet dat er geen conflict met attributen is.
De levensduur van registratie en opgehaalde resultaten scheiden
De registratie van resolvers en de geladen relatiedata die elk model vasthoudt, zijn twee verschillende dingen.
Als je een resolver overschrijft nadat
$order->acmeNotes al is geladen, worden de resultaten die dat model vasthoudt niet automatisch bijgewerkt. Moet je opnieuw ophalen, verwijder dan eerst de relatieresultaten en gebruik daarna bijvoorbeeld load('acmeNotes').
Ga er evenmin van uit dat resolvers verdwijnen door de applicatie in Testbench opnieuw op te bouwen. Gebruik voor tests die de ingeschakelde en uitgeschakelde situatie vergelijken een aparte model-fixture of de procesisolatie van PHPUnit, zodat je niet afhankelijk bent van eerdere registraties.
Zowel de registratie als de SQL testen
Controleer met Orchestra Testbench niet alleen de registratie door de provider, maar ook de foreign key en het daadwerkelijk ophalen van de relatie. Hieronder een voorbeeld vantests/DynamicRelationsTest.php. Zorg dat de klassen van de package en de testnamespace via Composer geladen kunnen worden, en voorzie in PDO en de SQLite-driver.
OrderFixture, maar de foreign key blijft order_id in plaats van order_fixture_id. Zo kun je controleren dat er niet wordt vertrouwd op afleiding uit de klassenaam.
Controleer in de CI van de package daarnaast de volgende grensgevallen.
- Bij een aparte, niet-geregistreerde fixture wordt geen relatie toegevoegd als
order_modelnullis. - Overerving naar kindmodellen, en de prioriteit wanneer alleen op het kind een resolver met dezelfde naam is geregistreerd.
- Bij een conflict met bestaande methoden of attributen is de beoogde API bruikbaar.
- Het verschil na het opnieuw registreren van de resolver tussen een model waarop de relatie al eens is geladen en een nieuw model.
- Kolomtypes, indexen en foreign key-constraints op de ondersteunde databases, en primaire sleutels die door de gebruiker zijn gewijzigd.
Geraadpleegde primaire bronnen
- Officiële Laravel-documentatie: Dynamic Relationships
- Officiële Laravel-documentatie: Package Development
- HasRelationships: registratie, zoeken via overerving en sleutelafhandeling
- Model: dynamische aanroepen en het wissen van de boot-status
- HasAttributes: prioriteit van attributen en het vasthouden van relatieresultaten
- Builder: relaties opbouwen bij eager loading
- QueriesRelationships: voorwaarden op het bestaan van relaties
- HasOneOrMany: foreign key instellen bij create
- Officiële Laravel-tests: DatabaseEloquentDynamicRelationsTest