v13.42.0. De basis van relaties vind je in de Introductie tot Eloquent-relaties. Hoe je van buitenaf relaties toevoegt aan de modellen van de gebruikende app, lees je in Dynamische relaties registreren vanuit packages.
Typenamen en relatienamen scheiden
Een polymorfe relatie combineert de primaire sleutel van het gerelateerde model met een typenaam om naar meerdere soorten modellen te verwijzen. Als reacties bijvoorbeeld bij artikelen of video’s kunnen horen, heb je naastcommentable_id ook commentable_type nodig.
Zonder registratie wordt normaal gesproken een volledig gekwalificeerde klassenaam zoals
Acme\Publishing\Models\Article als type gebruikt. Na het registreren van een alias wordt acme-publishing.article gebruikt, bijvoorbeeld bij het aanmaken van nieuwe relaties. Registratie alleen herschrijft de waarden in bestaande rijen niet.
Een morph map maakt geen relatiemethoden of tabellen aan. Modellen, kolommen en indexen maak je zoals gewoonlijk aan.
Alleen package-specifieke aliassen registreren
In het voorbeeld isAcme\Publishing\ via Composer PSR-4 gekoppeld aan src/. Definieer eerst src/Models/Article.php.
src/Models/Comment.php definieer je de omgekeerde relatie.
acme_articles krijgt id, title en timestamps, acme_comments krijgt id, body, commentable_type, commentable_id en timestamps. De primaire sleutels in dit voorbeeld zijn integers. Je kunt de kolommen en de samengestelde index van $table->morphs('commentable') gebruiken, maar stem bij UUID’s en vergelijkbare opzetten de kolomtypes af op het gerelateerde model.
Registreer daarna de alias in boot() van een gewone, niet-uitgestelde service provider, src/PublishingServiceProvider.php.
$alias is acme-publishing.article en $class is Article::class. Zolang Comment zelf niet het gerelateerde model van een andere polymorfe relatie wordt, hoef je voor dit voorbeeld geen alias voor Comment te registreren.
De regels voor samenvoegen en naamconflicten begrijpen
Relation::morphMap() werkt op een statische map. Bij het standaard samenvoegen worden de nieuwe en de bestaande array gecombineerd met $map + static::$morphMap.
Geeft een package
false mee als tweede argument, dan wist het de registraties van andere packages en van de gebruikende app. Behoud bij normale registratie de bestaande map en kies een alias die de vendor en het package herkenbaar maakt, in plaats van een generieke naam zoals article. Gebruik dezelfde alias niet opnieuw voor een andere betekenis.
Geef je alleen een lijst klassenamen mee, dan instantieert Laravel elk model en gebruikt het de tabelnaam als sleutel. Packages die het opslagformaat ook los willen koppelen van wijzigingen in de tabelnaam, declareren de koppeling tussen alias en klasse expliciet, zoals in het voorbeeld hierboven.
De standaard getMorphClass() zoekt naar de concrete modelklasse. Een subklasse van Article erft niet automatisch de alias van de bovenliggende klasse. Packages die het vervangen van modellen ondersteunen, hebben een ontwerp nodig dat de daadwerkelijk gebruikte klassen in de map registreert.
Maak de strikte modus een beleid voor de hele app
enforceMorphMap() uit de officiële documentatie registreert aliassen en roept requireMorphMap(true) in één stap aan. Het is geen verplichting die alleen voor het package geldt.
In de standaardimplementatie van
v13.42.0 werkt de strikte modus in beide richtingen.
- Van model naar typenaam:
getMorphClass()gooit eenClassMorphViolationExceptionvoor niet-geregistreerde modellen. De ingebouwde generieke klassePivotheeft een uitzondering, maar gewone modellen en eigen pivots krijgen die vrijstelling niet. - Van opgeslagen typenaam naar klasse:
getActualClassNameForMorph()gooit dezelfde exception voor waarden die geen sleutel in de map zijn. Dit wordt ook gebruikt bij het gewone ophalen viamorphTo()en bij eager loading. Ook als de klasse als “waarde” in de map staat, kan een oude volledig gekwalificeerde klassenaam die geen “sleutel” is niet worden geladen.
morphMap() en laat de keuze om de map app-breed af te dwingen aan de gebruikende app over. Dwingt de gebruikende app die al af, zet hem dan niet vanuit het package terug met requireMorphMap(false).
Migreer bestaande data voordat je de compatibiliteit beoordeelt
Voor nieuwe installaties en voor upgrades van bestaande apps gelden andere voorwaarden bij het invoeren van aliasregistratie. Ook de officiële documentatie waarschuwt dat je bij de invoering bestaande volledig gekwalificeerde klassenamen naar aliassen moet omzetten.Alleen de omgekeerde opvraging controleren is niet genoeg
Met de strikte modus uitgeschakeld kun je het artikel van een reactie mogelijk nog steeds ophalen metmorphTo(), zolang de klasse in de oude commentable_type bestaat. Maar morphMany() aan de kant van het artikel filtert op de waarde van getMorphClass(). Na het registreren van de alias zoekt het naar acme-publishing.article, dus rijen met de oude klassenaam verdwijnen uit de lijst met reacties van het artikel.
MorphOneOrMany legt de typenaam vast bij het aanmaken en gebruikt die zowel in de queryvoorwaarden als bij het aanmaken van nieuwe records. Ook als oude en nieuwe typenamen door elkaar voorkomen, zoekt het niet automatisch met OR naar alle waarden.
Neem de upgradeprocedure op in de release
Het volgende fragment zet de oude klassenaam om naar de alias. Implementeer het als datamigratie voor de betreffende app en voer het niet automatisch uit vanuit een service provider.- Breng de betreffende
*_type-kolommen en opgeslagen waarden in kaart en zorg vóór de update voor een back-up, een telling van de rijen en een herstelprocedure. Verwijzen andere tabellen naar hetzelfde model, neem die dan ook mee. - Leg voor de eerste invoering een overstapprocedure vast, inclusief het stoppen van schrijfacties of onderhoud, zodat processen die de oude klassenaam schrijven en processen die de alias schrijven niet tegelijk draaien. Overweeg bij grote tabellen ook update-locks en verwerking in batches.
- Schakel de map en de data om en zet de typenamen om zonder de waarden van de primaire sleutels te wijzigen. Leg de oude klassenaam in de migratiecode vast als de string die in het verleden daadwerkelijk is opgeslagen, niet als
Article::class, waarvan de betekenis verandert bij toekomstige hernoemingen. - Controleer het aantal resterende oude waarden, nieuwe opslagacties, opvragingen in beide richtingen en eager loading, en werk langlopende workers bij voordat je de schrijfacties hervat.
- Controleer bij een rollback of de teruggezette code de opgeslagen alias kan oplossen. Alleen de Composer-versie terugzetten zet de typenamen in de database niet terug.
Opgeslagen waarden en opvragingen in beide richtingen testen
Controleer met Orchestra Testbench niet alleen de map, maar ook de in de database opgeslagen waarden en opvragingen in beide richtingen. Het volgende voorbeeld gaat ervan uit dat het package via Composer wordt geautoload en dat de PDO SQLite-driver beschikbaar is. De provider wordt expliciet in de test geregistreerd, los van het controleren van package discovery.- Het registreren van de provider schakelt de strikte modus niet uit wanneer de gebruikende app die al heeft ingeschakeld.
- Het gepubliceerde opslagformaat verandert niet bij conflicten rond dezelfde alias, meerdere aliassen voor dezelfde klasse en een vervangen concreet model.
- Met fixtures die zijn opgeslagen met de klassenaam van vóór de upgrade blijven na de migratie geen oude waarden over, en kloppen de opvragingen in beide richtingen en de aantallen.
- De echte upgradeprocedure, inclusief queue workers, kan worden hersteld met de combinatie van teruggezette code en data.
Geraadpleegde primaire bronnen
- Officiële Laravel-documentatie: Custom Polymorphic Types
- Officiële Laravel-documentatie: Package Development
- Relation: samenvoegen van de map en strikte modus
- HasRelationships: typenamen ophalen en klassen oplossen
- MorphOneOrMany: typenaam vasthouden, queryvoorwaarden en opslaan
- MorphTo: typeresolutie bij eager loading
- Officiële Laravel-tests: morph maps registreren en oplossen
- Officiële Laravel-tests: lezen en schrijven in de strikte modus