Förstå modellrelationer i Laravel Eloquent

Modeller och deras relationer är hjärtat i Laravel Eloquent. Om de ger dig svårt eller om du inte kan hitta en enkel, vänlig och komplett guide, börja här!

Sitter på andra sidan av sin programmeringsartikel är det lätt för författaren att låtsas eller spränga aura av expertis/prestige som plattformen ger. Men jag ska vara ärlig – jag hade extremt svårt lära sig Laravel, om så bara för att det var mitt första ramverk i full stack. En anledning var att jag inte använde den på jobbet och utforskade den av nyfikenhet; så jag skulle göra ett försök, komma till en punkt, bli förvirrad, ge upp och så småningom glömma allt. Jag måste ha gjort detta 5-6 gånger innan det började bli meningsfullt för mig (naturligtvis hjälper inte dokumentationen).

Men det som fortfarande inte var vettigt var vältalig. Eller åtminstone, relationerna mellan modeller (eftersom Eloquent är för stor för att lära sig helt). Exempel på modellering av författare och blogginlägg är ett skämt eftersom riktiga projekt är mycket mer komplexa; tyvärr använder de officiella dokumenten samma (eller liknande) exempel. Eller även om jag stötte på någon användbar artikel/resurs, så var förklaringen så dålig eller så illa saknad att det helt enkelt inte var någon mening.

(Förresten, jag har blivit attackerad för att ha attackerat den officiella dokumentationen tidigare, så om du har liknande idéer, här är mitt standardsvar: kolla in Django-dokumentationen och prata sedan med mig.)

Så småningom, bit för bit, kom det ihop och blev vettigt. Jag kunde äntligen modellera projekt ordentligt och använda modellerna bekvämt. Så en dag stötte jag på några snygga samlingsknep som gör det här arbetet trevligare. I den här artikeln har jag för avsikt att täcka allt, börja från grunderna och sedan täcka alla möjliga användningsfall som du kommer att stöta på i verkliga projekt.

Varför är vältaliga modellrelationer svåra?

Tyvärr stöter jag på alldeles för många Laravel-utvecklare som inte förstår modeller ordentligt.

Men varför?

Även idag, när det finns en explosion av kurser, artiklar och videor om Laravel, är den övergripande förståelsen dålig. Jag tycker att det är en viktig punkt och är värd att reflektera över.

Om du frågar mig kommer jag att säga att vältaliga modellrelationer inte alls är svåra. Åtminstone sett ur definitionen av ”hårt”. Liveschemamigreringar är svåra; det är svårt att skriva en ny mallmotor; Det är svårt att bidra med kod till kärnan i Laravel. Jämfört med dessa, lära sig och använda en ORM . . . det kan inte vara svårt! 🤭🤭

Vad som faktiskt händer är att PHP-utvecklare som lär sig Laravel tycker att det är svårt med Eloquent. Det är den verkliga underliggande frågan, och enligt min åsikt finns det flera faktorer som bidrar till detta (hård, impopulär åsiktsvarning!):

  • Före Laravel har exponeringen för ett ramverk för de flesta PHP-utvecklare varit CodeIgniter (det är fortfarande Levande, förresten, även om det har blivit mer Laravel/CakePHP-likt). I den äldre CodeIgniter-gemenskapen (om det fanns en) var ”bästa praxis” att direkt fästa SQL-frågor där det behövdes. Och även om vi har en ny CodeIgniter idag, har vanorna förts över. Som ett resultat, när man lär sig Laravel, är idén med en ORM 100 % ny för PHP-utvecklare.
  • Om man kasserar den mycket lilla andelen PHP som exponeras för ramverk som Yii, CakePHP, etc., är de återstående vana vid att arbeta i grundläggande PHP eller i en miljö som WordPress. Och här igen, ett OOP-baserat tankesätt existerar inte, så ett ramverk, en tjänstebehållare, ett designmönster, en ORM. . . dessa är främmande begrepp.
  • Det finns lite eller inget koncept för kontinuerligt lärande i PHP-världen. Den genomsnittliga utvecklaren är nöjd med att arbeta med konfigurationer med en enda server genom att använda relationsdatabaser och utfärda frågor skrivna som strängar. Asynkron programmering, webbsockets, HTTP 2/3, Linux (glöm Docker), enhetstestning, domändriven design – allt detta är främmande idéer för en överväldigande andel PHP-utvecklare. Som ett resultat händer det inte att läsa upp om något nytt och utmanande, till den grad att man tycker att det är bekvämt, när man möter vältalig.
  • Den övergripande förståelsen av databaser och modellering är också dålig. Eftersom databasdesign är direkt, oskiljaktigt kopplad till Eloquent-modeller, höjer den svårighetsnivån högre.

Jag menar inte att vara hård och generalisera globalt – det finns utmärkta PHP-utvecklare också, och många av dem, men deras totala andel är mycket låg.

Om du läser det här betyder det att du har passerat alla dessa barriärer, stött på Laravel och bråkat med Eloquent.

Grattis! 👏

Du är nästan där. Alla byggstenar är på plats och vi behöver bara gå igenom dem i rätt ordning och detaljer. Med andra ord, låt oss börja på databasnivå.

Databasmodeller: Relationer och kardinalitet

För att göra saker enkelt, låt oss anta att vi endast arbetar med relationsdatabaser genom hela den här artikeln. En anledning är att ORM:er ursprungligen utvecklades för relationsdatabaser; den andra anledningen är att RDBMS fortfarande är överväldigande populära.

Datamodell

Låt oss först förstå datamodeller bättre. Idén om en modell (eller en datamodell, för att vara mer exakt), kommer från databasen. Ingen databas, ingen data och så ingen datamodell. Och vad är en datamodell? Helt enkelt är det sättet du bestämmer dig för att lagra/strukturera din data. Till exempel, i en e-handelsbutik kan du lagra allt i ett gigantiskt bord (HEMSK praxis, men tyvärr inte ovanligt i PHP-världen); det skulle vara din datamodell. Du kan också dela upp data i 20 huvud- och 16 anslutande tabeller; det är också en datamodell.

Observera också att hur data är strukturerad i databasen inte behöver matcha 100 % hur den är ordnad i ramverkets ORM. Men strävan är alltid att hålla saker så nära som möjligt så att vi inte har en sak till att tänka på när vi utvecklar.

Kardinalitet

Låt oss också få bort denna term snabbt: kardinalitet. Det syftar bara på ”räkna”, löst uttryckt. Alltså, 1, 2, 3. . . kan alla vara kardinalitet av något. Slutet av berättelsen. Låt oss fortsätta!

Relationer

Nu, när vi lagrar data i någon typ av system, finns det sätt som datapunkter kan relateras till varandra. Jag vet att det här låter abstrakt och tråkigt, men håll ut med mig lite. Sättet som olika dataobjekt är sammankopplade på kallas relationer. Låt oss först se några exempel utan databas så att vi är övertygade om att vi helt förstår idén.

  • Om vi ​​lagrar allt i en array är ett möjligt samband: nästa dataobjekt har ett index som är större än det föregående indexet med 1.
  • Om vi ​​lagrar data i ett binärt träd är ett möjligt samband att barnträdet till vänster alltid har mindre värden än föräldranodens (om vi väljer att behålla trädet på det sättet).
  • Om vi ​​lagrar data som en array av arrayer av lika längd kan vi härma en matris, och då blir dess egenskaper relationerna för våra data.

Så vi ser att ordet ”relation”, i datasammanhang, inte har en fast betydelse. Faktum är att om två personer tittade på samma data, kanske de identifierar två mycket olika datarelationer (hej, statistik!) och båda kan vara giltiga.

Relationsdatabaser

Baserat på alla termer vi har diskuterat hittills kan vi äntligen prata om något som har en direktlänk till modeller i ett webbramverk (Laravel) — relationsdatabaser. För de flesta av oss är den primära databasen som används MySQL, MariaDB, PostgreSQL, MSSQL, SQL Server, SQLite eller något i den stilen. Vi kan också vagt veta att dessa kallas RDBMS men de flesta av oss har glömt vad det egentligen betyder och varför spelar det någon roll.

”R” i RDBMS står för Relational, naturligtvis. Detta är inte en godtyckligt vald term; genom detta lyfter vi fram det faktum att dessa databassystem är designade för att fungera effektivt med relationer mellan lagrade data. Faktum är att ”relation” här har en strikt matematisk betydelse, och även om ingen utvecklare behöver bry sig om det, hjälper det att veta att det finns en rigorös matematisk fundament under dessa typer av databaser.

Utforska dessa resurser för att lära dig SQL och NoSQL.

Okej, så vi vet av erfarenhet att data i RDBMS lagras som tabeller. Var finns då relationerna?

Typer av relationer i RDBMS

Detta är kanske den viktigaste delen av hela ämnet Laravel och modellrelationer. Om du inte förstår detta kommer Eloquent aldrig att vara vettigt, så var uppmärksam under de närmaste minuterna (det är inte ens så svårt).

En RDBMS tillåter oss att ha relationer mellan data – på en databasnivå. Det betyder att dessa relationer inte är opraktiska/imaginära/subjektiva och kan skapas eller slutas av olika personer med samma resultat.

Samtidigt finns det vissa funktioner/verktyg inom ett RDBMS som gör att vi kan skapa och upprätthålla dessa relationer, till exempel:

  • Primärnyckel
  • Främmande nyckel
  • Begränsningar

Jag vill inte att den här artikeln ska bli en kurs i databaser, så jag antar att du vet vad dessa begrepp är. Om inte, eller om du känner dig skakig i ditt självförtroende, rekommenderar jag den här vänliga videon (utforska gärna hela serien):

Som det händer är dessa RDBMS-liknande relationer också de vanligaste som förekommer i verkliga applikationer (inte alltid, eftersom ett socialt nätverk bäst modelleras som en graf och inte som en samling tabeller). Så låt oss ta en titt på dem en efter en och också försöka förstå var de kan vara användbara.

En-till-en relation

I nästan varje webbapplikation finns det användarkonton. Följande är också sant (i allmänhet) om användare och konton:

  • En användare kan bara ha ett konto.
  • Ett konto kan endast ägas av en användare.

Ja, vi kan argumentera för att en person kan registrera sig med en annan e-post och därmed skapa två konton, men ur webbapplikationens perspektiv är det två olika personer med två olika konton. Applikationen kommer till exempel inte att visa ett kontos data i ett annat.

Vad allt detta hårklyveri betyder är – om du har en situation som denna i din applikation och du använder en relationsdatabas, måste du utforma den som en en-till-en-relation. Observera att ingen tvingar dig på konstgjord väg – det finns en tydlig situation i affärsdomänen och du råkar använda en relationsdatabas . . . först när båda dessa villkor är uppfyllda når du ett en-till-en-förhållande.

För det här exemplet (användare och konton) är det så här vi kan implementera denna relation när vi skapar schemat:

CREATE TABLE users(
    id INT NOT NULL AUTO_INCREMENT,
    email VARCHAR(100) NOT NULL,
    password VARCHAR(100) NOT NULL,
    PRIMARY KEY(id)
);

CREATE TABLE accounts(
    id INT NOT NULL AUTO_INCREMENT,
    role VARCHAR(50) NOT NULL,
    PRIMARY KEY(id),
    FOREIGN KEY(id) REFERENCES users(id)
);

Lägger du märke till tricket här? Det är ganska ovanligt när man bygger appar generellt, men i kontotabellen har vi fält-ID:t som både primärnyckel och främmande nyckel! Den främmande nyckelegenskapen länkar den till användartabellen (naturligtvis 🙄) medan den primära nyckelegenskapen gör id-kolumnen unik – en sann en-till-en-relation!

Visst, troheten i detta förhållande är inte garanterad. Till exempel finns det inget som hindrar mig från att lägga till 200 nya användare utan att lägga till en enda post i kontotabellen. Om jag gör det får jag ett ett-till-noll förhållande! 🤭🤭 Men inom ramarna för ren struktur är det det bästa vi kan göra. Om vi ​​vill förhindra att lägga till användare utan konton måste vi ta hjälp av någon form av programmeringslogik, antingen i form av databasutlösare eller valideringar som genomförs av Laravel.

Om du börjar bli stressad har jag några mycket bra råd:

  • Ta det lugnt. Så långsam som du behöver. Istället för att försöka avsluta den här artikeln och de 15 andra som du har bokmärkt för idag, håll dig till den här. Låt det ta 3, 4, 5 dagar om det är vad som krävs – ditt mål bör vara att ta bort vältaliga modellrelationer från din lista för alltid. Du har hoppat från artikel till artikel tidigare, slösat bort flera hundra timmar och ändå hjälpte det inte. Så gör något annorlunda den här gången. 😇
  • Även om den här artikeln handlar om Laravel Eloquent, kommer allt det mycket senare. Grunden för det hela är databasschema, så vårt fokus bör ligga på att få det rätt först. Om du inte kan arbeta enbart på databasnivå (förutsatt att det inte finns några ramverk i världen), kommer modeller och relationer aldrig att vara helt vettiga. Så, glöm Laravel nu. Fullständigt. Vi pratar bara om och gör databasdesign för tillfället. Ja, jag kommer att göra Laravel-referenser då och då, men ditt jobb är att ignorera dem helt om de komplicerar bilden för dig.
  • Läs lite mer om databaser och vad de erbjuder senare. Index, prestanda, triggers, underliggande datastrukturer och deras beteende, cachelagring, relationer i MongoDB . . . vilka tangentiella ämnen du än kan ta upp kommer att hjälpa dig som ingenjör. Kom ihåg att rammodeller bara är spökskal; den verkliga funktionaliteten hos en plattform kommer från dess underliggande databaser.

En-till-många-relation

Jag är inte säker på om du insåg detta, men det här är den typen av relation vi alla intuitivt skapar i vårt dagliga arbete. När vi skapar en ordertabell (ett hypotetiskt exempel), till exempel för att lagra en främmande nyckel till användartabellen, skapar vi en en-till-många-relation mellan användare och order. Varför är det så? Tja, titta på det igen ur perspektivet av vem som kan ha hur många: en användare får ha mer än en beställning, vilket i stort sett är hur all e-handel fungerar. Och sett från motsatt sida säger relationen att en order bara kan tillhöra en användare, vilket också är väldigt vettigt.

I datamodellering, RDBMS-böcker och systemdokumentation representeras denna situation schematiskt så här:

Lägger du märke till de tre linjerna som gör en sorts treudd? Det här är symbolen för ”många”, så det här diagrammet säger att en användare kan ha många beställningar.

Förresten, dessa ”många” och ”en” räkningar som vi stöter på upprepade gånger är vad som kallas kardinaliteten i en relation (minns du det här ordet från ett tidigare avsnitt?). Återigen, för den här artikeln har termen ingen användning, men det hjälper att känna till konceptet ifall det dyker upp under intervjuer eller vidare läsning.

Enkelt, eller hur? Och när det gäller faktisk SQL är det också enkelt att skapa denna relation. Faktum är att det är mycket enklare än fallet med ett en-till-en-förhållande!

CREATE TABLE users( 
    id INT NOT NULL AUTO_INCREMENT, 
    email VARCHAR(100) NOT NULL, 
    password VARCHAR(100) NOT NULL, 
    PRIMARY KEY(id) 
);

CREATE TABLE orders( 
    id INT NOT NULL AUTO_INCREMENT, 
    user_id INT NOT NULL, 
    description VARCHAR(50) NOT NULL, 
    PRIMARY KEY(id), 
    FOREIGN KEY(user_id) REFERENCES users(id) 
);

Ordertabellen lagrar användar-ID för varje beställning. Eftersom det inte finns någon begränsning (restriktion) att användar-ID:n i ordertabellen måste vara unika, betyder det att vi kan upprepa ett enda ID många gånger. Det är detta som skapar en-till-många-relationen, och inte någon mystisk magi som är gömd under. Användar-ID:n lagras på ett dumt sätt i ordertabellen, och SQL har inte något koncept för en-till-många, en-till-en, etc. Men när vi väl lagrar data på det här sättet, kan tänka sig att det finns en en-till-många-relation.

Förhoppningsvis är det vettigt nu. Eller åtminstone mer vettigt än tidigare. 😅 Kom ihåg att precis som allt annat är detta bara en fråga om övning, och när du har gjort detta 4-5 gånger i verkliga situationer kommer du inte ens tänka på det.

Många-till-många relationer

Nästa typ av relation som uppstår i praktiken är den så kallade många-till-många-relationen. Återigen, innan vi oroar oss för ramverk eller ens dyker in i databaser, låt oss tänka på en verklig analog: böcker och författare. Tänk på din favoritförfattare; de har skrivit mer än en bok, eller hur? Samtidigt är det ganska vanligt att se flera författare samarbeta om en bok (åtminstone inom facklitteraturgenren). Så en författare kan skriva många böcker, och många författare kan skriva en bok. Mellan de två enheterna (bok och författare) bildar detta ett många-till-många-förhållande.

Givetvis är det högst osannolikt att du skapar en verklig app som involverar bibliotek eller böcker och författare, så låt oss tänka på några fler exempel. I en B2B-miljö beställer en tillverkare varor från en leverantör och får i sin tur en faktura. Fakturan kommer att innehålla flera rader, var och en av dem anger kvantitet och artikel som levereras; till exempel 5-tums rörbitar x 200, etc. I den här situationen har artiklar och fakturor ett många-till-många-förhållande (tänk på det och övertyga dig själv). I ett fleet management-system kommer fordon och förare att ha en liknande relation. På en e-handelssajt kan användare och produkter ha en många-till-många-relation om vi tänker på funktionalitet som favoriter eller önskelistor.

Helt rätt, hur skapar man nu denna många-till-många-relation i SQL? Baserat på vår kunskap om hur en-till-många-relationen fungerar, kan det vara frestande att tro att vi ska lagra främmande nycklar till den andra tabellen i båda tabellerna. Vi stöter dock på stora problem om vi försöker göra detta. Ta en titt på det här exemplet där böcker är författare ska ha ett många-till-många-förhållande:

Vid första anblicken ser allt bra ut – böcker mappas till författare exakt på ett många-till-många sätt. Men titta noga på författarnas tabelldata: bok-id 12 och 13 är båda skrivna av Peter M. (författare-id 2), varför vi inte har något annat val än att upprepa posterna. Inte bara har författartabellen nu problem med dataintegritet (egentligt normalisering och allt det där), värdena i id-kolumnen upprepas nu. Det betyder att i designen vi har valt kan det inte finnas någon primärnyckelkolumn (eftersom primärnycklar inte kan ha dubbletter av värden), och allt faller isär.

Det är klart att vi behöver ett nytt sätt att göra detta på, och tack och lov har detta problem redan lösts. Eftersom att lagra främmande nycklar direkt i båda tabellerna skruvar upp saker och ting. Det rätta sättet att skapa många-till-många-relationer i RDBMS är att skapa en så kallad ”sammanfogningstabell”. Tanken är i grunden att låta de två originalborden stå ostört och skapa ett tredje bord för att demonstrera många-till-många-mappningen.

Låt oss göra om det misslyckade exemplet för att innehålla en sammanfogningstabell:

Lägg märke till att det har skett drastiska förändringar:

  • Antalet kolumner i författartabellen reduceras.
  • Antalet kolumner i boktabellen minskas.
  • Antalet rader i författartabellen minskas eftersom det inte längre behövs upprepas.
  • En ny tabell som heter authors_books har dykt upp, som innehåller information om vilket författare-id som är kopplat till vilket bok-id. Vi kunde ha döpt sammanfogningstabellen vad som helst, men enligt konventionen är resultatet av att helt enkelt sammanfoga de två tabellerna den representerar med ett understreck.

Den sammanfogade tabellen har ingen primärnyckel och innehåller i de flesta fall bara två kolumner – ID från de två tabellerna. Det är nästan som om vi tog bort kolumnerna för främmande nyckel från vårt tidigare exempel och klistrade in dem i den här nya tabellen. Eftersom det inte finns någon primärnyckel kan det bli så mycket upprepning som behövs för att registrera alla relationer.

Nu kan vi se med våra ögon hur sammanfogningstabellen visar relationerna tydligt, men hur kommer vi åt dem i våra applikationer? Hemligheten är kopplad till namnet — anslutningstabell. Det här är inte en kurs i SQL-frågor, så jag kommer inte att fördjupa mig i det, men tanken är att om du vill ha alla böcker av en viss författare i en, effektiv fråga, ansluter du dig till tabellerna i samma ordning –> författare, författare_böcker och böcker. Tabellerna författare och authors_books sammanfogas över id- respektive author_id-kolumnerna, medan tabellerna authors_books och books är sammanfogade på book_id- respektive id-kolumnerna.

Utmattande, ja. Men titta på den ljusa sidan — vi har avslutat all nödvändig teori/grundarbete vi behövde göra innan vi tog oss an vältaliga modeller. Och låt mig påminna dig om att allt det här inte är valfritt! Att inte veta databasdesign kommer att lämna dig i vältalig förvirringsland för alltid. Dessutom, vad Eloquent än gör eller försöker göra, speglar dessa databasnivådetaljer perfekt, så det är lätt att se varför det är en meningslös övning att försöka lära sig vältalig samtidigt som man flyr från RDBMS.

Skapa modellrelationer i Laravel Eloquent

Slutligen, efter en omväg som varade cirka 70 000 miles, har vi nått den punkt där vi kan prata om Eloquent, dess modeller och hur man skapar/använder dem. Nu lärde vi oss i föregående del av artikeln att allt börjar med databasen och hur du modellerar din data. Detta fick mig att inse att jag borde använda ett enda, komplett exempel där jag startar ett nytt projekt. Samtidigt vill jag att det här exemplet ska vara verkligt, och inte om bloggar och författare eller böcker och hyllor (som också är verkliga, men som har gjorts ihjäl).

Låt oss föreställa oss en butik som säljer mjuka leksaker. Låt oss också anta att vi har fått kravdokumentet, från vilket vi kan identifiera dessa fyra enheter i systemet: användare, order, fakturor, artiklar, kategorier, underkategorier och transaktioner. Ja, det kommer sannolikt att vara mer komplikationer inblandade, men låt oss bara lägga det åt sidan och fokusera på hur vi går från ett dokument till en app.

När huvudenheterna i systemet väl har identifierats måste vi tänka på hur de förhåller sig till varandra, i termer av de databasrelationer vi har diskuterat hittills. Här är de jag kan tänka mig:

  • Användare och beställningar: En till många.
  • Beställningar och fakturor: En till en. Jag inser att den här inte är skuren och torkad, och beroende på din affärsdomän kan det finnas en en till många, en många till en eller en många till många relation. Men när det kommer till din genomsnittliga, lilla e-handelsbutik, kommer en order bara att resultera i en faktura och vice versa.
  • Beställningar och föremål: Många till många.
  • Artiklar och kategorier: Många till en. Återigen är det inte så på stora e-handelssajter, men vi har en liten verksamhet.
  • Kategorier och underkategorier: en till många. Återigen, du hittar de flesta verkliga exempel som motsäger detta, men hey, vältalig är tillräckligt svårt som det är, så låt oss inte göra datamodelleringen svårare!
  • Beställningar och transaktioner: En till många. Jag skulle också vilja lägga till dessa två punkter som motivering för mitt val: 1) Vi kunde också ha lagt till en relation mellan transaktioner och fakturor. Det är bara ett beslut om datamodellering. 2) Varför en till många här? Tja, det är vanligt att en orderbetalning misslyckas av någon anledning och lyckas nästa gång. I det här fallet har vi två transaktioner skapade för den beställningen. Om vi ​​vill visa dessa misslyckade transaktioner eller inte är ett affärsbeslut, men det är alltid en bra idé att samla in värdefull data.

Finns det några andra relationer? Tja, många fler relationer är möjliga, men de är inte praktiska. Till exempel kan vi säga att en användare har många transaktioner, så det bör finnas en relation mellan dem. Saken att inse här är att det redan finns ett indirekt förhållande: användare -> order -> transaktioner, och generellt sett är det tillräckligt bra eftersom RDBMS är bestar i att gå med i tabeller. För det andra skulle skapa denna relation innebära att lägga till en user_id-kolumn i transaktionstabellen. Om vi ​​gjorde detta för alla möjliga direkta relationer, skulle vi lägga till mycket mer belastning på databasen (i form av mer lagring, speciellt om UUID används, och underhålla index), kedja ner det övergripande systemet. Visst, om företaget säger att de behöver transaktionsdata och behöver det inom 1,5 sekunder, kan vi bestämma oss för att lägga till den relationen och påskynda saker och ting (avvägningar, avvägningar …).

Och nu, mina damer och herrar, är det dags att skriva själva koden!

Laravel-modellrelationer — verkligt exempel med kod

Nästa fas i den här artikeln handlar om att göra oss smutsiga – men på ett användbart sätt. Vi kommer att plocka upp samma databasenheter som i det tidigare e-handelsexemplet, och vi kommer att se hur modeller i Laravel skapas och ansluts, direkt från installationen av Laravel!

Naturligtvis antar jag att du har din utvecklingsmiljö inställd och att du vet hur du installerar och använder Composer för att hantera beroenden.

$ composer global require laravel/installer -W
$ laravel new model-relationships-study

Dessa två konsolkommandon installerar Laravel-installationsprogrammet (-W-delen används för att uppgradera eftersom jag redan hade en äldre version installerad). Och om du är nyfiken, när du skriver, är Laravel-versionen som installerades 8.5.9. Ska du få panik och uppgradera också? Jag skulle avråda från det, eftersom jag inte förväntar mig några större förändringar mellan Laravel 5 och Laravel 8 i samband med vår ansökan. Vissa saker har förändrats och kommer att påverka den här artikeln (som Model Factory), men jag tror att du kommer att kunna portera koden.

Eftersom vi redan har tänkt igenom datamodellen och deras relationer kommer delen av att skapa modellerna vara trivial. Och du kommer också att se (jag låter som en trasig skiva nu!) hur det speglar databasschemat eftersom det är 100% beroende av det!

Med andra ord måste vi först skapa migreringarna (och modellfilerna) för alla modellerna, som kommer att tillämpas på databasen. Senare kan vi arbeta med modellerna och ta tag i relationerna.

Så vilken modell börjar vi med? Den enklaste och minst anslutna, förstås. I vårt fall betyder det användarmodellen. Eftersom Laravel levereras med den här modellen (och inte kan fungera utan den 🤣), låt oss ändra migreringsfilen och även rensa upp modellen för att passa våra enkla behov.

Här är migreringsklassen:

class CreateUsersTable extends Migration
{
    public function up()
    {
        Schema::create('users', function (Blueprint $table) {
            $table->id();
            $table->string('name');
        });
    }
}

Eftersom vi faktiskt inte bygger ett projekt behöver vi inte komma in på lösenord, is_active och allt det där. Vår användartabell kommer bara att ha två kolumner: id och användarens namn.

Låt oss skapa migreringen för kategori härnäst. Eftersom Laravel tillåter oss att generera modellen också i ett enda kommando, kommer vi att dra fördel av det, även om vi inte kommer att röra modellfilen för tillfället.

$ php artisan make:model Category -m
Model created successfully.
Created Migration: 2021_01_26_093326_create_categories_table

Och här är migrationsklassen:

class CreateCategoriesTable extends Migration
{
    public function up()
    {
        Schema::create('categories', function (Blueprint $table) {
            $table->id();
            $table->string('name');
        });
    }
}

Om du är förvånad över avsaknaden av down()-funktionen, var det inte; i praktiken slutar du sällan med att använda det som att ta bort en kolumn eller tabell eller ändra en kolumntyp resulterar i dataförlust som inte kan återställas. Under utvecklingen kommer du att släppa hela databasen och sedan köra om migreringarna. Men vi avviker, så låt oss gå tillbaka och ta itu med nästa enhet. Eftersom underkategorier är direkt relaterade till kategorier tycker jag att det är en bra idé att göra det härnäst.

$ php artisan make:model SubCategory -m
Model created successfully.
Created Migration: 2021_01_26_140845_create_sub_categories_table

Okej, låt oss nu fylla i migreringsfilen:

class CreateSubCategoriesTable extends Migration
{
    public function up()
    {
        Schema::create('sub_categories', function (Blueprint $table) {
            $table->id();
            $table->string('name');

            $table->unsignedBigInteger('category_id');
            $table->foreign('category_id')
                ->references('id')
                ->on('categories')
                ->onDelete('cascade');
        });
    }
}

Som du kan se lägger vi till en separat kolumn här, kallad kategori_id, som lagrar ID:n från kategoritabellen. Inga priser för att gissa, detta skapar en till många relationer på databasnivå.

Nu är det saker som gäller:

$ php artisan make:model Item -m
Model created successfully.
Created Migration: 2021_01_26_141421_create_items_table

Och migrationen:

class CreateItemsTable extends Migration
{
    public function up()
    {
        Schema::create('items', function (Blueprint $table) {
            $table->id();
            $table->string('name');
            $table->text('description');
            $table->string('type');
            $table->unsignedInteger('price');
            $table->unsignedInteger('quantity_in_stock');

            $table->unsignedBigInteger('sub_category_id');
            $table->foreign('sub_category_id')
                ->references('id')
                ->on('sub_categories')
                ->onDelete('cascade');
        });
    }
}

Om du känner att saker och ting borde göras annorlunda är det bra. Två personer kommer sällan att komma på exakt samma schema och arkitektur. Notera en sak, som är en sorts bästa praxis: Jag har lagrat priset som ett heltal.

Varför?

Nåväl, folk insåg att hantering av floatdivisioner och allt var fult och felbenäget på databassidan, så de började lagra priset i termer av den minsta valutaenheten. Om vi ​​till exempel verkade i USD skulle prisfältet här representera cent. Genom hela systemet kommer värdena och beräkningarna att vara i cent; först när det är dags att visa för användaren eller skicka en PDF via e-post kommer vi att dividera med 100 och avrunda. Smart, va?

Hur som helst, lägg märke till att ett objekt är kopplat till en underkategori i en många-till-en-relation. Den är också länkad till en kategori . . . indirekt via sin underkategori. Vi kommer att se solida demonstrationer av all denna gymnastik, men för tillfället måste vi uppskatta koncepten och se till att vi är 100 % tydliga.

Nästa upp är ordermodellen och dess migrering:

$ php artisan make:model Order -m
Model created successfully.
Created Migration: 2021_01_26_144157_create_orders_table

För korthetens skull kommer jag bara att ta med några av de viktiga områdena i migrationen. Med det menar jag, en orders detaljer kan innehålla väldigt många saker, men vi kommer att begränsa dem till ett fåtal så att vi kan fokusera på konceptet modellrelationer.

class CreateOrdersTable extends Migration
{
    public function up()
    {
        Schema::create('orders', function (Blueprint $table) {
            $table->id();
            $table->string('status');
            $table->unsignedInteger('total_value');
            $table->unsignedInteger('taxes');
            $table->unsignedInteger('shipping_charges');

            $table->unsignedBigInteger('user_id');
            $table->foreign('user_id')
                ->references('id')
                ->on('users')
                ->onDelete('cascade');
        });
    }
}

Ser bra ut, men vänta lite! Var är föremålen i denna beställning? Som vi konstaterade tidigare finns det ett många-till-många-förhållande mellan beställningar och varor, så en enkel främmande nyckel fungerar inte. Lösningen är ett så kallat skarvbord eller mellanbord. Med andra ord behöver vi ett sammanfogningsbord för att lagra många-till-många-mappningen mellan beställningar och artiklar. Nu, i Laravel-världen, finns det en inbyggd konvention som vi följer för att spara tid: Om jag skapar en ny tabell genom att använda singularformen av de två tabellnamnen, placera dem i ordboksordning och slå ihop dem med ett understreck, Laravel kommer automatiskt att känna igen det som kopplingsbordet.

I vårt fall kommer sammanfogningstabellen att kallas item_order (ordet ”artikel” kommer före ”order” i en ordbok). Dessutom, som förklarats tidigare, kommer denna sammanfogningstabell normalt endast att innehålla två kolumner, främmande nycklar till varje tabell.

Vi skulle kunna skapa en modell + migrering här, men modellen kommer aldrig att användas eftersom det är mer en meta-grej. Därför skapar vi en ny migration i Laravel och berättar vad som är vad.

$ php artisan make:migration create_item_order_table --create="item_order"
Created Migration: 2021_01_27_093127_create_item_order_table

Detta resulterar i en ny migrering, som vi kommer att ändra enligt följande:

class CreateItemOrderTable extends Migration
{
    public function up()
    {
        Schema::create('item_order', function (Blueprint $table) {
            $table->unsignedBigInteger('order_id');
            $table->foreign('order_id')
                ->references('id')
                ->on('orders')
                ->onDelete('cascade');
            
            $table->unsignedBigInteger('item_id');
            $table->foreign('item_id')
                ->references('id')
                ->on('items')
                ->onDelete('cascade');    
        });
    }
}

Hur man faktiskt kommer åt dessa relationer genom vältaliga metodanrop är ett ämne för senare, men lägg märke till att vi måste först mödosamt, för hand, skapa dessa främmande nycklar. Utan dessa finns det ingen vältalig och det finns ingen ”smart” i Laravel. 🙂

Är vi där än? Ja, nästan . . .

Vi har bara ett par modeller till att oroa sig för. Den första är fakturatabellen, och du kommer ihåg att vi bestämde oss för att göra det till en en-till-en-relation med beställningar.

$ php artisan make:model Invoice -m
Model created successfully.
Created Migration: 2021_01_27_101116_create_invoices_table

I de mycket tidiga avsnitten av den här artikeln såg vi att ett sätt att genomdriva en en-till-en-relation är att även göra den primära nyckeln på den underordnade tabellen till den främmande nyckeln. I praktiken är det knappast någon som tar detta alltför försiktiga tillvägagångssätt, och folk utformar i allmänhet schemat som de skulle för en en-till-många-relation. Min uppfattning är att en medelstrategi är bättre; gör bara den främmande nyckeln unik och du har sett till att modermodellens ID:n inte kan upprepas:

class CreateInvoicesTable extends Migration
{
    public function up()
    {
        Schema::create('invoices', function (Blueprint $table) {
            $table->id();
            $table->timestamp('raised_at')->nullable();
            $table->string('status');
            $table->unsignedInteger('totalAmount');

            $table->unsignedBigInteger('order_id')->unique();
            $table->foreign('order_id')
                ->references('id')
                ->on('orders')
                ->onDelete('cascade')
                ->unique();
        });
    }
}

Och ja, för femtonde gången är jag medveten om att det saknas mycket i den här fakturatabellen; Men vårt fokus här är att se hur modellrelationer fungerar och inte att designa en hel databas.

Okej, så vi har nått den punkt där vi behöver skapa den slutliga migreringen av vårt system (hoppas jag!). Fokus ligger nu på Transaktionsmodellen, som vi tidigare bestämt är kopplad till Ordermodellen. Här kommer förresten en övning för dig: Ska Transaktionsmodellen istället kopplas till Fakturamodellen? Varför och varför inte? 🙂

$ php artisan make:model Transaction -m
Model created successfully.
Created Migration: 2021_01_31_145806_create_transactions_table

Och migrationen:

class CreateTransactionsTable extends Migration
{
    public function up()
    {
        Schema::create('transactions', function (Blueprint $table) {
            $table->id();
            $table->timestamp('executed_at');
            $table->string('status');
            $table->string('payment_mode');
            $table->string('transaction_reference')->nullable();

            $table->unsignedBigInteger('order_id');
            $table->foreign('order_id')
                ->references('id')
                ->on('orders')
                ->onDelete('cascade');
        });
    }
}

Puh! Det var en del hårt arbete. . . låt oss köra migreringarna och se hur vi gör i databasens ögon.

$ php artisan migrate:fresh
Dropped all tables successfully.
Migration table created successfully.
Migrating: 2014_10_12_000000_create_users_table
Migrated:  2014_10_12_000000_create_users_table (3.45ms)
Migrating: 2021_01_26_093326_create_categories_table
Migrated:  2021_01_26_093326_create_categories_table (2.67ms)
Migrating: 2021_01_26_140845_create_sub_categories_table
Migrated:  2021_01_26_140845_create_sub_categories_table (3.83ms)
Migrating: 2021_01_26_141421_create_items_table
Migrated:  2021_01_26_141421_create_items_table (6.09ms)
Migrating: 2021_01_26_144157_create_orders_table
Migrated:  2021_01_26_144157_create_orders_table (4.60ms)
Migrating: 2021_01_27_093127_create_item_order_table
Migrated:  2021_01_27_093127_create_item_order_table (3.05ms)
Migrating: 2021_01_27_101116_create_invoices_table
Migrated:  2021_01_27_101116_create_invoices_table (3.95ms)
Migrating: 2021_01_31_145806_create_transactions_table
Migrated:  2021_01_31_145806_create_transactions_table (3.54ms)

Prisad vare Herren! 🙏🏻🙏🏻 Det verkar som om vi har överlevt rättegångens ögonblick.

Och med det är vi redo att gå vidare till att definiera modellrelationer! För det måste vi gå tillbaka till listan som vi skapade tidigare, som beskriver typen av direkta relationer mellan modeller (tabeller).

Till att börja med har vi konstaterat att det finns en en-till-många-relation mellan användare och beställningar. Vi kan bekräfta detta genom att gå till beställningarnas migreringsfil och se närvaron av fältet user_id där. Det här fältet är det som skapar relationen, eftersom alla relationer som vi är intresserade av att upprätta måste respekteras av databasen först; resten (vältalande syntax och var man ska skriva vilken funktion) är bara ren formalitet.

Relationen finns med andra ord redan där. Vi behöver bara säga åt Eloquent att göra den tillgänglig under körning. Låt oss börja med beställningsmodellen, där vi förklarar att den tillhör användarmodellen:

<?php

namespace AppModels;

use IlluminateDatabaseEloquentFactoriesHasFactory;
use IlluminateDatabaseEloquentModel;

class Order extends Model
{
    use HasFactory;

    public function user() {
        return $this->belongsTo(User::class);
    }
}

Syntaxen måste vara bekant för dig; vi deklarerar en funktion som heter user(), som tjänar till att komma åt användaren som äger denna order (funktionsnamnet kan vara vad som helst; det är vad den returnerar som spelar roll). Tänk tillbaka ett ögonblick – om det inte fanns någon databas och inga främmande nycklar, skulle ett uttalande som $this->belongsTo vara meningslöst. Det är bara för att det finns en främmande nyckel på ordertabellen som Laravel kan använda det user_id för att slå upp användaren med samma id och returnera det. I sig själv, utan samarbetet med databasen, kan Laravel inte skapa relationer ur tomma intet.

Nu skulle det också vara trevligt att kunna skriva $user->order för att komma åt en användares order. Det betyder att vi måste gå till användarmodellen och skriva ut en funktion för den ”många” delen av denna en-till-många-relation:

<?php

namespace AppModels;

use IlluminateDatabaseEloquentFactoriesHasFactory;
use IlluminateDatabaseEloquentModel;

class User extends Model
{
    use HasFactory;
    public $timestamps = false;

    public function orders() {
        return $this->hasMany(Order::class);
    }
}

Ja, jag har kraftigt modifierat standardanvändarmodellen eftersom vi inte behöver alla andra funktioner för den här handledningen. Hur som helst, User-klassen har nu en metod som heter orders(), som säger att en användare kan associeras med flera order. I ORM-världen säger vi att orders()-relationen här är inversen av user()-relationen vi hade på Order-modellen.

Men, vänta lite! Hur fungerar denna relation? Jag menar, det finns inget på databasnivå som har flera anslutningar som går ut från användartabellen till ordertabellen.

I själva verket finns det en befintlig anslutning, och det visar sig att det räcker på egen hand — referensen för främmande nyckel lagrad i ordertabellen! Det är, när vi säger något som $user->orders, så träffar Laravel funktionen orders() och vet genom att titta på den att det finns en främmande nyckel på ordertabellen. Sedan gör den en SELECT * FROM order WHERE user_id = 23 och returnerar frågeresultaten som en samling. Naturligtvis är hela poängen med att ha en ORM att glömma SQL, men vi bör inte helt glömma att den underliggande basen är RDBMS som kör SQL-frågor.

Låt oss sedan gå igenom order- och faktureringsmodellerna, där vi har en en-till-en-relation:

<?php

namespace AppModels;

use IlluminateDatabaseEloquentFactoriesHasFactory;
use IlluminateDatabaseEloquentModel;

class Order extends Model
{
    use HasFactory;
    public $timestamps = false;

    public function user() {
        return $this->belongsTo(User::class);
    }

    public function invoice() {
        return $this->hasOne(Invoice::class);
    }
}

Och fakturamodellen:

<?php

namespace AppModels;

use IlluminateDatabaseEloquentFactoriesHasFactory;
use IlluminateDatabaseEloquentModel;

class Invoice extends Model
{
    use HasFactory;
    public $timestamps = false;

    public function order() {
        return $this->belongsTo(Order::class);
    }
}

Lägg märke till att på databasnivån, såväl som nästan på den vältaliga nivån, är det en typisk en-till-många-relation; vi har precis lagt till några kontroller för att se till att det förblir en-till-en.

Vi kommer nu till en annan typ av relation — många-till-många mellan beställningar och varor. Kom ihåg att vi redan har skapat en mellantabell som heter item_order som lagrar mappningen mellan primärnycklarna. Om så mycket har gjorts på rätt sätt är det trivialt att definiera relationen och arbeta med den. Enligt Laravel-dokumenten, för att definiera en många-till-många-relation, måste dina metoder returnera en belongsToMany()-instans.

Så, i artikelmodellen:

<?php

namespace AppModels;

use IlluminateDatabaseEloquentFactoriesHasFactory;
use IlluminateDatabaseEloquentModel;

class Item extends Model
{
    use HasFactory;
    public $timestamps = false;

    public function orders() {
        return $this->belongsToMany(Order::class);
    }
}

Överraskande nog är det omvända förhållandet nästan identiskt:

class Order extends Model
{
    /* ... other code */
    
    public function items() {
        return $this->belongsToMany(Item::class);
    }
}

Och det är allt! Så länge vi har följt namnkonventionerna korrekt, kan Laravel härleda mappningarna såväl som deras plats.

Eftersom alla de tre grundläggande typerna av relationer har täckts (en-till-en, en-till-många, många-till-många), kommer jag att sluta skriva ut metoderna för andra modeller, eftersom de kommer att finnas längs med samma rader. Låt oss istället skapa fabrikerna för dessa modeller, skapa lite dummydata och se dessa relationer i aktion!

Hur gör vi det? Nåväl, låt oss ta den snabba och smutsiga vägen och slänga allt i standardsåddfilen. Sedan, när vi kör migreringarna, kommer vi att köra sådden också. Så här ser min DatabaseSeeder.php-fil ut:

<?php

namespace DatabaseSeeders;

use IlluminateDatabaseSeeder;
use AppModelsCategory;
use AppModelsSubCategory;
use AppModelsItem;
use AppModelsOrder;
use AppModelsInvoice;
use AppModelsUser;
use Faker;

class DatabaseSeeder extends Seeder
{
    public function run()
    {
        $faker = FakerFactory::create();

        // Let's make two users
        $user1 = User::create(['name' => $faker->name]);
        $user2 = User::create(['name' => $faker->name]);

        // Create two categories, each having two subcategories
        $category1 = Category::create(['name' => $faker->word]);
        $category2 = Category::create(['name' => $faker->word]);

        $subCategory1 = SubCategory::create(['name' => $faker->word, 'category_id' => $category1->id]);
        $subCategory2 = SubCategory::create(['name' => $faker->word, 'category_id' => $category1->id]);

        $subCategory3 = SubCategory::create(['name' => $faker->word, 'category_id' => $category2->id]);
        $subCategory4 = SubCategory::create(['name' => $faker->word, 'category_id' => $category2->id]);

        // After categories, well, we have items
        // Let's create two items each for sub-category 2 and 4
        $item1 = Item::create([
            'sub_category_id' => 2,
            'name' => $faker->name,
            'description' => $faker->text,
            'type' => $faker->word,
            'price' => $faker->randomNumber(2),
            'quantity_in_stock' => $faker->randomNumber(2),
        ]);

        $item2 = Item::create([
            'sub_category_id' => 2,
            'name' => $faker->name,
            'description' => $faker->text,
            'type' => $faker->word,
            'price' => $faker->randomNumber(3),
            'quantity_in_stock' => $faker->randomNumber(2),
        ]);

        $item3 = Item::create([
            'sub_category_id' => 4,
            'name' => $faker->name,
            'description' => $faker->text,
            'type' => $faker->word,
            'price' => $faker->randomNumber(4),
            'quantity_in_stock' => $faker->randomNumber(2),
        ]);

        $item4 = Item::create([
            'sub_category_id' => 4,
            'name' => $faker->name,
            'description' => $faker->text,
            'type' => $faker->word,
            'price' => $faker->randomNumber(1),
            'quantity_in_stock' => $faker->randomNumber(3),
        ]);

        // Now that we have users and items, let's make user1 place a couple of orders
        $order1 = Order::create([
            'status' => 'confirmed',
            'total_value' => $faker->randomNumber(3),
            'taxes' => $faker->randomNumber(1),
            'shipping_charges' => $faker->randomNumber(2),
            'user_id' => $user1->id
        ]);

        $order2 = Order::create([
            'status' => 'waiting',
            'total_value' => $faker->randomNumber(3),
            'taxes' => $faker->randomNumber(1),
            'shipping_charges' => $faker->randomNumber(2),
            'user_id' => $user1->id
        ]);

        // now, assigning items to orders
        $order1->items()->attach($item1);
        $order1->items()->attach($item2);
        $order1->items()->attach($item3);
        
        $order2->items()->attach($item1);
        $order2->items()->attach($item4);

        // and finally, create invoices
        $invoice1 = Invoice::create([
            'raised_at' => $faker->dateTimeThisMonth(),
            'status' => 'settled',
            'totalAmount' => $faker->randomNumber(3),
            'order_id' => $order1->id,
        ]);
    }
}

Och nu ställer vi in ​​databasen igen och ser den:

$ php artisan migrate:fresh --seed
Dropped all tables successfully.
Migration table created successfully.
Migrating: 2014_10_12_000000_create_users_table
Migrated:  2014_10_12_000000_create_users_table (43.81ms)
Migrating: 2021_01_26_093326_create_categories_table
Migrated:  2021_01_26_093326_create_categories_table (2.20ms)
Migrating: 2021_01_26_140845_create_sub_categories_table
Migrated:  2021_01_26_140845_create_sub_categories_table (4.56ms)
Migrating: 2021_01_26_141421_create_items_table
Migrated:  2021_01_26_141421_create_items_table (5.79ms)
Migrating: 2021_01_26_144157_create_orders_table
Migrated:  2021_01_26_144157_create_orders_table (6.40ms)
Migrating: 2021_01_27_093127_create_item_order_table
Migrated:  2021_01_27_093127_create_item_order_table (4.66ms)
Migrating: 2021_01_27_101116_create_invoices_table
Migrated:  2021_01_27_101116_create_invoices_table (6.70ms)
Migrating: 2021_01_31_145806_create_transactions_table
Migrated:  2021_01_31_145806_create_transactions_table (6.09ms)
Database seeding completed successfully.

Okej! Nu är den sista delen av den här artikeln, där vi helt enkelt kommer åt dessa relationer och bekräftar allt vi har lärt oss hittills. Du kommer att bli glad över att veta (hoppas jag) att det här kommer att bli ett lätt och roligt avsnitt.

Och nu, låt oss starta den roligaste Laravel-komponenten – den interaktiva Tinker-konsolen!

$ php artisan tinker
Psy Shell v0.10.6 (PHP 8.0.0 — cli) by Justin Hileman
>>>

Få tillgång till en-till-en-modellrelationer i Laravel Eloquent

Okej, så låt oss först få tillgång till en-till-en-relationen vi har i våra beställnings- och fakturamodeller:

>>> $order = Order::find(1);
[!] Aliasing 'Order' to 'AppModelsOrder' for this Tinker session.
=> AppModelsOrder {#4108
     id: 1,
     status: "confirmed",
     total_value: 320,
     taxes: 5,
     shipping_charges: 12,
     user_id: 1,
   }
>>> $order->invoice
=> AppModelsInvoice {#4004
     id: 1,
     raised_at: "2021-01-21 19:20:31",
     status: "settled",
     totalAmount: 314,
     order_id: 1,
   }

Märker du något? Kom ihåg att så som det har gjorts på databasnivå är detta förhållande ett till många, om inte för de extra begränsningarna. Så Laravel kunde ha returnerat en samling objekt (eller bara ett objekt) som resultat, och det skulle vara tekniskt korrekt. MEN . . . vi har sagt till Laravel att det är en en-till-en-relation, så resultatet är en enda vältalig instans. Lägg märke till hur samma sak händer när du kommer åt det omvända förhållandet:

$invoice = Invoice::find(1);
[!] Aliasing 'Invoice' to 'AppModelsInvoice' for this Tinker session.
=> AppModelsInvoice {#3319
     id: 1,
     raised_at: "2021-01-21 19:20:31",
     status: "settled",
     totalAmount: 314,
     order_id: 1,
   }
>>> $invoice->order
=> AppModelsOrder {#4042
     id: 1,
     status: "confirmed",
     total_value: 320,
     taxes: 5,
     shipping_charges: 12,
     user_id: 1,
   }

Få tillgång till en-till-många-modellrelationer i Laravel Eloquent

Vi har en en-till-många-relation mellan användare och beställningar. Låt oss ”pyssla” med det nu och se resultatet:

>>> User::find(1)->orders;
[!] Aliasing 'User' to 'AppModelsUser' for this Tinker session.
=> IlluminateDatabaseEloquentCollection {#4291
     all: [
       AppModelsOrder {#4284
         id: 1,
         status: "confirmed",
         total_value: 320,
         taxes: 5,
         shipping_charges: 12,
         user_id: 1,
       },
       AppModelsOrder {#4280
         id: 2,
         status: "waiting",
         total_value: 713,
         taxes: 4,
         shipping_charges: 80,
         user_id: 1,
       },
     ],
   }
>>> Order::find(1)->user
=> AppModelsUser {#4281
     id: 1,
     name: "Dallas Kshlerin",
   }

Exakt som förväntat resulterar åtkomst av en användares order i en samling poster, medan det omvända bara producerar ett enda användarobjekt. En-till-många med andra ord.

Få tillgång till många-till-många modellrelationer i Laravel Eloquent

Låt oss nu utforska ett förhållande som är många-till-många. Vi har ett sådant förhållande mellan varor och beställningar:

>>> $item1 = Item::find(1);
[!] Aliasing 'Item' to 'AppModelsItem' for this Tinker session.
=> AppModelsItem {#4253
     id: 1,
     name: "Russ Kutch",
     description: "Deserunt voluptatibus omnis ut cupiditate doloremque. Perspiciatis officiis odio et accusantium alias aut. Voluptatum provident aut ut et.",
     type: "adipisci",
     price: 26,
     quantity_in_stock: 65,
     sub_category_id: 2,
   }
>>> $order1 = Order::find(1);
=> AppModelsOrder {#4198
     id: 1,
     status: "confirmed",
     total_value: 320,
     taxes: 5,
     shipping_charges: 12,
     user_id: 1,
   }
>>> $order1->items
=> IlluminateDatabaseEloquentCollection {#4255
     all: [
       AppModelsItem {#3636
         id: 1,
         name: "Russ Kutch",
         description: "Deserunt voluptatibus omnis ut cupiditate doloremque. Perspiciatis officiis odio et accusantium alias aut. Voluptatum provident aut ut et.",
         type: "adipisci",
         price: 26,
         quantity_in_stock: 65,
         sub_category_id: 2,
         pivot: IlluminateDatabaseEloquentRelationsPivot {#4264
           order_id: 1,
           item_id: 1,
         },
       },
       AppModelsItem {#3313
         id: 2,
         name: "Mr. Green Cole",
         description: "Maxime beatae porro commodi fugit hic. Et excepturi natus distinctio qui sit qui. Est non non aut necessitatibus aspernatur et aspernatur et. Voluptatem possimus consequatur exercitationem et.",
         type: "pariatur",
         price: 381,
         quantity_in_stock: 82,
         sub_category_id: 2,
         pivot: IlluminateDatabaseEloquentRelationsPivot {#4260
           order_id: 1,
           item_id: 2,
         },
       },
       AppModelsItem {#4265
         id: 3,
         name: "Brianne Weissnat IV",
         description: "Delectus ducimus quia voluptas fuga sed eos esse. Rerum repudiandae incidunt laboriosam. Ea eius omnis autem. Cum pariatur aut voluptas sint aliquam.",
         type: "non",
         price: 3843,
         quantity_in_stock: 26,
         sub_category_id: 4,
         pivot: IlluminateDatabaseEloquentRelationsPivot {#4261
           order_id: 1,
           item_id: 3,
         },
       },
     ],
   }
>>> $item1->orders
=> IlluminateDatabaseEloquentCollection {#4197
     all: [
       AppModelsOrder {#4272
         id: 1,
         status: "confirmed",
         total_value: 320,
         taxes: 5,
         shipping_charges: 12,
         user_id: 1,
         pivot: IlluminateDatabaseEloquentRelationsPivot {#4043
           item_id: 1,
           order_id: 1,
         },
       },
       AppModelsOrder {#4274
         id: 2,
         status: "waiting",
         total_value: 713,
         taxes: 4,
         shipping_charges: 80,
         user_id: 1,
         pivot: IlluminateDatabaseEloquentRelationsPivot {#4257
           item_id: 1,
           order_id: 2,
         },
       },
     ],
   }

Denna utdata kan vara lite svindlande att läsa igenom men lägg märke till att artikel1 är en del av order1s artiklar, och vice versa, vilket är hur vi ställer upp saker och ting. Låt oss också kika in i den mellanliggande tabellen som lagrar mappningarna:

>>> use DB;
>>> DB::table('item_order')->select('*')->get();
=> IlluminateSupportCollection {#4290
     all: [
       {#4270
         +"order_id": 1,
         +"item_id": 1,
       },
       {#4276
         +"order_id": 1,
         +"item_id": 2,
       },
       {#4268
         +"order_id": 1,
         +"item_id": 3,
       },
       {#4254
         +"order_id": 2,
         +"item_id": 1,
       },
       {#4267
         +"order_id": 2,
         +"item_id": 4,
       },
     ],
   }

Slutsats

Ja, det är det, verkligen! Det har varit en riktigt lång artikel, men jag hoppas att den har varit användbar. Är det allt man behöver veta om Laravel-modeller?

Tyvärr nej. Kaninhålet är riktigt, riktigt djupt, och det finns många fler utmanande koncept som polymorfa relationer och prestandajustering, och vad som helst, som du kommer att stöta på när du växer som en Laravel-utvecklare. För nu räcker det som den här artikeln tar upp för 70 % av utvecklarna 70 % av tiden, grovt sett. Det kommer att ta riktigt lång tid innan du känner ett behov av att uppgradera dina kunskaper.

Med den varningen ur vägen vill jag att du tar bort denna viktigaste insikt: ingenting är mörk magi eller utom räckhåll i programmering. Det är bara det att vi inte förstår grunderna och hur saker är uppbyggda, vilket gör att vi kämpar och känner oss frustrerade.

Så . . . ?

Investera i dig själv! Kurser, böcker, artiklar, andra programmeringsgemenskaper (Python är min #1 rekommendation) — använd de resurser du kan hitta och konsumera dem regelbundet om det går långsamt. Ganska snart kommer antalet fall där du sannolikt missar det hela att minska drastiskt.

Okej, nog med predikan. Ha en bra dag! 🙂