Forstå modellforhold i Laravel Eloquent

Modeller og deres forhold er hjertet til Laravel Eloquent. Hvis de gir deg en vanskelig tid eller du ikke klarer å finne en enkel, vennlig og komplett guide, start her!

Når du sitter på den andre siden av programmeringsartikkelen, er det lett for forfatteren å late som eller sprenge auraen av ekspertise/prestisje plattformen gir. Men jeg skal være ærlig – jeg hadde det ekstremt vanskelig lære Laravel, om ikke annet fordi det var mitt første fullstack-rammeverk. En grunn var at jeg ikke brukte den på jobben og utforsket den av nysgjerrighet; så jeg ville gjøre et forsøk, komme til et punkt, bli forvirret, gi opp og til slutt glemme alt. Jeg må ha gjort dette 5-6 ganger før det begynte å gi mening for meg (selvfølgelig hjelper ikke dokumentasjonen).

Men det som fortsatt ikke ga mening var veltalende. Eller i det minste relasjonene mellom modellene (fordi Eloquent er for stor til å lære helt). Eksempler på modellering av forfattere og blogginnlegg er en spøk fordi virkelige prosjekter er langt mer komplekse; Dessverre bruker de offisielle dokumentene de samme (eller lignende) eksemplene. Eller selv om jeg kom over en nyttig artikkel/ressurs, var forklaringen så dårlig eller manglet så mye at det rett og slett ikke var noen vits.

(Forresten, jeg har blitt angrepet for å ha angrepet den offisielle dokumentasjonen før, så hvis du har lignende ideer, her er standardsvaret mitt: sjekk ut Django-dokumentasjonen og snakk med meg.)

Til slutt, bit for bit, kom det sammen og ga mening. Jeg var endelig i stand til å modellere prosjekter ordentlig og bruke modellene komfortabelt. Så en dag kom jeg over noen fine samlingstriks som gjør dette arbeidet mer behagelig. I denne artikkelen har jeg tenkt å dekke alt, fra det aller grunnleggende og deretter dekke alle mulige brukstilfeller du vil møte i virkelige prosjekter.

Hvorfor er veltalende modellforhold vanskelige?

Dessverre kommer jeg over alt for mange Laravel-utviklere som ikke forstår modeller ordentlig.

Men hvorfor?

Selv i dag, når det er en eksplosjon av kurs, artikler og videoer om Laravel, er den generelle forståelsen dårlig. Jeg synes det er et viktig poeng og er verdt å reflektere litt over.

Hvis du spør meg, vil jeg si at veltalende modellforhold ikke er vanskelig i det hele tatt. I hvert fall sett fra perspektivet til definisjonen av «hard». Live-skjemamigreringer er vanskelige; å skrive en ny malmotor er vanskelig; Det er vanskelig å bidra med kode til kjernen av Laravel. Sammenlignet med disse, læring og bruk av en ORM . . . vel, det kan ikke være vanskelig! 🤭🤭

Det som faktisk skjer er at PHP-utviklere som lærer Laravel synes Eloquent er vanskelig. Det er det virkelige underliggende problemet, og etter min mening er det flere faktorer som bidrar til dette (hard, upopulær meningsvarsel!):

  • Før Laravel har eksponeringen for et rammeverk for de fleste PHP-utviklere vært CodeIgniter (det er fortsatt i live, forresten, selv om det har blitt mer Laravel/CakePHP-aktig). I det eldre CodeIgniter-fellesskapet (hvis det fantes en), var «beste praksis» å sette SQL-spørringer direkte der det var nødvendig. Og selv om vi har en ny CodeIgniter i dag, har vanene tatt over. Som et resultat, når du lærer Laravel, er ideen om en ORM 100 % ny for PHP-utviklere.
  • Hvis du forkaster den svært lille prosentandelen av PHP som er utsatt for rammeverk som Yii, CakePHP, etc., er de resterende vant til å jobbe i kjerne-PHP eller i et miljø som WordPress. Og her igjen, en OOP-basert tankegang eksisterer ikke, så et rammeverk, en tjenestebeholder, et designmønster, en ORM . . . dette er fremmede konsepter.
  • Det er lite eller ingen begrep om kontinuerlig læring i PHP-verdenen. Den gjennomsnittlige utvikleren er fornøyd med å jobbe med enkeltserveroppsett ved å bruke relasjonsdatabaser og utstede spørringer skrevet som strenger. Asynkron programmering, web-sockets, HTTP 2/3, Linux (glem Docker), enhetstesting, domenedrevet design – alt dette er fremmede ideer for en overveldende andel PHP-utviklere. Som et resultat skjer det ikke å lese seg opp om noe nytt og utfordrende, til det punktet at man synes det er behagelig, når man møter veltalende.
  • Den generelle forståelsen av databaser og modellering er også dårlig. Siden databasedesign er direkte, uatskillelig knyttet til Eloquent-modeller, hever det vanskelighetsgrensen høyere.

Jeg mener ikke å være tøff og generalisere globalt – det finnes også utmerkede PHP-utviklere, og mange av dem, men den totale prosentandelen deres er veldig lav.

Hvis du leser dette, betyr det at du har krysset alle disse barrierene, kommet over Laravel og rotet med Eloquent.

Gratulerer! 👏

Du er nesten der. Alle byggeklossene er på plass, og vi trenger bare å gå gjennom dem i riktig rekkefølge og detaljer. Med andre ord, la oss starte på databasenivå.

Databasemodeller: Relasjoner og kardinalitet

For å gjøre ting enkelt, la oss anta at vi kun jobber med relasjonsdatabaser gjennom denne artikkelen. En grunn er at ORM-er opprinnelig ble utviklet for relasjonsdatabaser; den andre grunnen er at RDBMS fortsatt er overveldende populære.

Datamodell

La oss først forstå datamodeller bedre. Ideen om en modell (eller en datamodell, for å være mer presis), kommer fra databasen. Ingen database, ingen data, og så ingen datamodell. Og hva er en datamodell? Ganske enkelt er det måten du bestemmer deg for å lagre/strukturere dataene dine på. For eksempel, i en e-handelsbutikk, kan du lagre alt i en gigantisk tabell (FORFERDELIG praksis, men dessverre ikke uvanlig i PHP-verdenen); det ville være din datamodell. Du kan også dele dataene i 20 hoved- og 16 tilkoblingstabeller; det er også en datamodell.

Vær også oppmerksom på at måten data er strukturert i databasen ikke trenger å samsvare 100 % med hvordan de er ordnet i rammeverkets ORM. Men innsatsen er alltid å holde ting så nært som mulig, slik at vi ikke har en ting til å være oppmerksom på når vi utvikler.

Kardinalitet

La oss også få dette begrepet ut av veien raskt: kardinalitet. Det refererer bare til «telle», løst sagt. Så, 1, 2, 3. . . kan alt være kardinaliteten til noe. Slutt på historien. La oss fortsette å bevege oss!

Forhold

Nå, når vi lagrer data i en hvilken som helst type system, er det måter datapunkter kan relateres til hverandre. Jeg vet at dette høres abstrakt og kjedelig ut, men tål meg litt. Måtene forskjellige dataelementer henger sammen på er kjent som relasjoner. La oss først se noen eksempler som ikke er databaser, slik at vi er overbevist om at vi fullt ut forstår ideen.

  • Hvis vi lagrer alt i en matrise, er en mulig sammenheng: neste dataelement har en indeks som er større enn den forrige indeksen med 1.
  • Hvis vi lagrer data i et binært tre, er en mulig sammenheng at barnetreet til venstre alltid har mindre verdier enn foreldrenodens (hvis vi velger å opprettholde treet på den måten).
  • Hvis vi lagrer data som en rekke arrays av lik lengde, kan vi etterligne en matrise, og da blir dens egenskaper relasjonene for dataene våre.

Så vi ser at ordet «forhold», i sammenheng med data, ikke har en fast betydning. Faktisk, hvis to personer så på de samme dataene, kan de identifisere to svært forskjellige dataforhold (hei, statistikk!), og begge kan være gyldige.

Relasjonelle databaser

Basert på alle begrepene vi har diskutert til nå, kan vi endelig snakke om noe som har en direkte kobling til modeller i et nettrammeverk (Laravel) – relasjonsdatabaser. For de fleste av oss er den primære databasen som brukes MySQL, MariaDB, PostgreSQL, MSSQL, SQL Server, SQLite eller noe i den retning. Vi kan også vagt vite at disse kalles RDBMS, men de fleste av oss har glemt hva det faktisk betyr og hvorfor betyr det noe.

«R» i RDBMS står for Relational, selvfølgelig. Dette er ikke et vilkårlig valgt begrep; med dette fremhever vi det faktum at disse databasesystemene er designet for å fungere effektivt med relasjoner mellom lagrede data. Faktisk har «relasjon» her en streng matematisk betydning, og selv om ingen utviklere trenger å bry seg om det, hjelper det å vite at det er en streng matematisk fundament under disse typer databaser.

Utforsk disse ressursene for å lære SQL og NoSQL.

Ok, så vi vet av erfaring at data i RDBMS lagres som tabeller. Hvor er relasjonene da?

Typer relasjoner i RDBMS

Dette er kanskje den viktigste delen av hele temaet Laravel og modellforhold. Hvis du ikke forstår dette, vil Eloquent aldri gi mening, så vær oppmerksom på de neste minuttene (det er ikke engang så vanskelig).

En RDBMS lar oss ha relasjoner mellom data – på databasenivå. Dette betyr at disse relasjonene ikke er upraktiske/imaginære/subjektive og kan skapes eller utledes av forskjellige mennesker med samme resultat.

Samtidig er det visse muligheter/verktøy i et RDBMS som lar oss opprette og håndheve disse relasjonene, for eksempel:

  • Primærnøkkel
  • Utenlandsk nøkkel
  • Begrensninger

Jeg vil ikke at denne artikkelen skal bli et kurs i databaser, så jeg antar at du vet hva disse konseptene er. Hvis ikke, eller i tilfelle du føler deg skjelven i din selvtillit, anbefaler jeg denne vennlige videoen (utforsk gjerne hele serien):

Som det skjer, er disse RDBMS-stil-relasjonene også de vanligste som forekommer i virkelige applikasjoner (ikke alltid, siden et sosialt nettverk er best modellert som en graf og ikke som en samling tabeller). Så la oss ta en titt på dem en etter en og også prøve å forstå hvor de kan være nyttige.

En-til-en forhold

I nesten alle nettapplikasjoner er det brukerkontoer. Følgende er også sant (generelt sett) om brukerne og kontoene:

  • En bruker kan bare ha én konto.
  • En konto kan bare eies av én bruker.

Ja, vi kan argumentere for at en person kan registrere seg med en annen e-post og dermed opprette to kontoer, men fra webapplikasjonens perspektiv er det to forskjellige personer med to forskjellige kontoer. Applikasjonen vil for eksempel ikke vise én kontos data i en annen.

Hva alt dette hårklyveriet betyr er – hvis du har en situasjon som dette i applikasjonen din og du bruker en relasjonsdatabase, må du designe den som en en-til-en-relasjon. Merk at ingen tvinger deg kunstig – det er en klar situasjon i forretningsdomenet, og du bruker tilfeldigvis en relasjonsdatabase . . . først når begge disse betingelsene er oppfylt, strekker du deg etter et en-til-en-forhold.

For dette eksemplet (brukere og kontoer) er dette hvordan vi kan implementere dette forholdet når vi lager skjemaet:

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)
);

Legger du merke til trikset her? Det er ganske uvanlig når man bygger apper generelt, men i kontotabellen har vi felt-ID-en satt som både primærnøkkel og fremmednøkkel! Fremmednøkkelegenskapen kobler den til brukertabellen (selvfølgelig 🙄), mens primærnøkkelegenskapen gjør id-kolonnen unik – et ekte en-til-en-forhold!

Riktignok er trofastheten til dette forholdet ikke garantert. For eksempel er det ingenting som hindrer meg i å legge til 200 nye brukere uten å legge til en enkelt oppføring i kontotabellen. Hvis jeg gjør det, ender jeg opp med et en-til-null forhold! 🤭🤭 Men innenfor grensene for ren struktur er det det beste vi kan gjøre. Hvis vi vil forhindre å legge til brukere uten kontoer, må vi ta hjelp fra en slags programmeringslogikk, enten i form av databaseutløsere eller valideringer håndhevet av Laravel.

  Slik fjerner du DoorDash-kort som betalingsmetode

Hvis du begynner å stresse, har jeg noen veldig gode råd:

  • Ta det rolig. Så sakte du trenger. I stedet for å prøve å fullføre denne artikkelen og de 15 andre som du har bokmerket for i dag, hold deg til denne. La det ta 3, 4, 5 dager hvis det er det som skal til – målet ditt bør være å slå veltalende modellforhold av listen for alltid. Du har hoppet fra artikkel til artikkel før, kastet bort flere hundre timer, og likevel hjalp det ikke. Så gjør noe annerledes denne gangen. 😇
  • Selv om denne artikkelen handler om Laravel Eloquent, kommer alt mye senere. Grunnlaget for det hele er databaseskjema, så vårt fokus bør være på å få det riktig først. Hvis du ikke kan jobbe utelukkende på databasenivå (forutsatt at det ikke finnes noen rammeverk i verden), vil modeller og relasjoner aldri gi full mening. Så glem Laravel for nå. Helt. Vi snakker bare om og gjør databasedesign foreløpig. Ja, jeg kommer til å lage Laravel-referanser nå og da, men jobben din er å ignorere dem fullstendig hvis de kompliserer bildet for deg.
  • Senere kan du lese litt mer om databaser og hva de tilbyr. Indekser, ytelse, triggere, underliggende datastrukturer og deres oppførsel, caching, relasjoner i MongoDB . . . uansett hvilke tangentielle emner du kan dekke vil hjelpe deg som ingeniør. Husk at rammemodeller bare er spøkelsesskjell; den virkelige funksjonaliteten til en plattform kommer fra dens underliggende databaser.

En-til-mange forhold

Jeg er ikke sikker på om du skjønte dette, men dette er den typen forhold vi alle intuitivt skaper i vårt daglige arbeid. Når vi oppretter en ordretabell (et hypotetisk eksempel), for eksempel for å lagre en fremmednøkkel til brukertabellen, oppretter vi en en-til-mange-relasjon mellom brukere og ordre. Hvorfor det? Vel, se på det igjen fra perspektivet om hvem som kan ha hvor mange: én bruker har lov til å ha mer enn én ordre, noe som stort sett er hvordan all e-handel fungerer. Og sett fra motsatt side sier forholdet at en ordre kun kan tilhøre én bruker, noe som også gir mye mening.

I datamodellering, RDBMS-bøker og systemdokumentasjon er denne situasjonen representert skjematisk slik:

Legg merke til de tre linjene som lager en slags trefork? Dette er symbolet for «mange», og derfor sier dette diagrammet at én bruker kan ha mange bestillinger.

Forresten, disse «mange» og «en» tellingene som vi møter gjentatte ganger er det som kalles kardinaliteten til et forhold (husker du dette ordet fra en tidligere del?). Igjen, for denne artikkelen har begrepet ingen bruk, men det hjelper å kjenne konseptet i tilfelle det dukker opp under intervjuer eller videre lesing.

Enkelt, ikke sant? Og når det gjelder faktisk SQL, er det også enkelt å skape dette forholdet. Faktisk er det mye enklere enn tilfellet med et en-til-en forhold!

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) 
);

Ordretabellen lagrer bruker-IDer for hver ordre. Siden det ikke er noen begrensning (restriksjon) på at bruker-ID-ene i ordretabellen må være unike, betyr det at vi kan gjenta en enkelt ID mange ganger. Det er dette som skaper en-til-mange-forholdet, og ikke noe mystisk magi som er skjult under. Bruker-ID-ene er lagret på en dum måte i ordretabellen, og SQL har ikke noe begrep om en-til-mange, en-til-en osv. Men når vi først lagrer data på denne måten, kan tenke seg at det er et en-til-mange forhold.

Forhåpentligvis gir det mening nå. Eller i det minste mer fornuftig enn før. 😅 Husk at akkurat som alt annet er dette bare et spørsmål om øvelse, og når du har gjort dette 4-5 ganger i virkelige situasjoner, vil du ikke engang tenke på det.

Mange-til-mange forhold

Den neste typen relasjoner som oppstår i praksis er den såkalte mange-til-mange-relasjonen. Nok en gang, før vi bekymrer oss for rammeverk eller til og med dykker inn i databaser, la oss tenke på en virkelig analog: bøker og forfattere. Tenk på favorittforfatteren din; de har skrevet mer enn én bok, ikke sant? Samtidig er det ganske vanlig å se flere forfattere samarbeide om en bok (i hvert fall i sakprosasjangeren). Så én forfatter kan skrive mange bøker, og mange forfattere kan skrive én bok. Mellom de to enhetene (bok og forfatter) danner dette et mange-til-mange-forhold.

Nå, gitt at du er svært usannsynlig å lage en virkelig app som involverer biblioteker eller bøker og forfattere, så la oss tenke på noen flere eksempler. I en B2B-setting bestiller en produsent varer fra en leverandør og mottar i sin tur en faktura. Fakturaen vil inneholde flere artikler, hver av dem viser antallet og varen som er levert; for eksempel 5-tommers rørstykker x 200 osv. I denne situasjonen har varer og fakturaer et mange-til-mange forhold (tenk deg om og overbevis deg selv). I et flåtestyringssystem vil kjøretøy og sjåfører ha et lignende forhold. På en e-handelsside kan brukere og produkter ha et mange-til-mange forhold hvis vi vurderer funksjonalitet som favoritter eller ønskelister.

Greit nok, nå hvordan lage dette mange-til-mange-forholdet i SQL? Basert på vår kunnskap om hvordan en-til-mange-forholdet fungerer, kan det være fristende å tenke at vi bør lagre fremmednøkler til den andre tabellen i begge tabellene. Vi får imidlertid store problemer hvis vi prøver å gjøre dette. Ta en titt på dette eksemplet der bøker er forfattere skal ha et mange-til-mange forhold:

Ved første øyekast ser alt bra ut – bøker er kartlagt til forfattere nøyaktig på en mange-til-mange måte. Men se nøye på forfatternes tabelldata: bok-id 12 og 13 er begge skrevet av Peter M. (forfatter-id 2), på grunn av dette har vi ikke noe annet valg enn å gjenta oppføringene. Ikke bare har forfattertabellen nå problemer med dataintegritet (egentlig normalisering og alt det), verdiene i id-kolonnen gjentas nå. Dette betyr at i designet vi har valgt, kan det ikke være noen primærnøkkelkolonne (fordi primærnøkler ikke kan ha dupliserte verdier), og alt faller fra hverandre.

Det er klart at vi trenger en ny måte å gjøre dette på, og heldigvis er dette problemet allerede løst. Siden lagring av fremmednøkler direkte i begge tabellene skruer opp ting, er den riktige måten å skape mange-til-mange-relasjoner i RDBMS ved å lage en såkalt «sammenføyningstabell». Tanken er i utgangspunktet å la de to originale bordene stå uforstyrret og lage et tredje bord for å demonstrere mange-til-mange-kartleggingen.

La oss gjøre om det mislykkede eksemplet for å inneholde en sammenføyningstabell:

Legg merke til at det har vært drastiske endringer:

  • Antall kolonner i forfattertabellen er redusert.
  • Antall kolonner i boktabellen reduseres.
  • Antall rader i forfattertabellen er redusert ettersom det ikke er behov for repetisjon lenger.
  • En ny tabell kalt forfattere_bøker har dukket opp, som inneholder informasjon om hvilken forfatter-ID som er koblet til hvilken bok-ID. Vi kunne ha kalt sammenføyningstabellen hva som helst, men etter konvensjon er resultatet av ganske enkelt å slå sammen de to tabellene den representerer, ved å bruke en understrek.

Sammenføyningstabellen har ingen primærnøkkel og inneholder i de fleste tilfeller bare to kolonner – IDer fra de to tabellene. Det er nesten som om vi fjernet de utenlandske nøkkelkolonnene fra vårt tidligere eksempel og limte dem inn i denne nye tabellen. Siden det ikke er noen primærnøkkel, kan det være så mye repetisjon som er nødvendig for å registrere alle relasjonene.

Nå kan vi se med øynene hvordan sammenføyningstabellen viser relasjonene tydelig, men hvordan får vi tilgang til dem i applikasjonene våre? Hemmeligheten er knyttet til navnet — sammenføyningstabell. Dette er ikke et kurs i SQL-spørringer, så jeg vil ikke dykke ned i det, men ideen er at hvis du vil ha alle bøkene av en bestemt forfatter i én, effektiv spørring, kan du SQL-føye sammen tabellene i samme rekkefølge –> forfattere, forfattere_bøker og bøker. Forfattere og forfattere_bøker-tabellene er slått sammen over henholdsvis id- og forfatter_id-kolonnene, mens tabellene forfattere_bøker og bøker er slått sammen på henholdsvis book_id- og id-kolonnene.

Utmattende, ja. Men se på den lyse siden – vi har fullført all nødvendig teori/grunnarbeid vi trengte å gjøre før vi tar fatt på veltalende modeller. Og la meg minne deg på at alt dette ikke er valgfritt! Å ikke vite databasedesign vil etterlate deg i veltalende forvirringsland for alltid. Dessuten, uansett hva Eloquent gjør eller prøver å gjøre, speiler disse detaljene på databasenivå perfekt, så det er lett å se hvorfor det å prøve å lære Eloquent mens du flykter fra RDBMS er en øvelse i nytteløshet.

Opprette modellforhold i Laravel Eloquent

Til slutt, etter en omvei som varte rundt 70 000 miles, har vi nådd det punktet hvor vi kan snakke om Eloquent, modellene og hvordan man lager/bruker dem. Nå lærte vi i forrige del av artikkelen at alt begynner med databasen og hvordan du modellerer dataene dine. Dette fikk meg til å innse at jeg burde bruke et enkelt, komplett eksempel der jeg starter et nytt prosjekt. Samtidig vil jeg at dette eksemplet skal være i den virkelige verden, og ikke om blogger og forfattere eller bøker og hyller (som også er i den virkelige verden, men som har blitt gjort til døde).

La oss forestille oss en butikk som selger kosedyr. La oss også anta at vi har fått kravdokumentet, som vi kan identifisere disse fire enhetene i systemet fra: brukere, bestillinger, fakturaer, varer, kategorier, underkategorier og transaksjoner. Ja, det er sannsynligvis mer komplikasjoner involvert, men la oss bare legge det til side og fokusere på hvordan vi går fra et dokument til en app.

Når hovedenhetene i systemet er identifisert, må vi tenke på hvordan de forholder seg til hverandre, i form av databaserelasjonene vi har diskutert så langt. Her er de jeg kan tenke på:

  • Brukere og bestillinger: En til mange.
  • Bestillinger og fakturaer: En til en. Jeg innser at denne ikke er kuttet og tørket, og avhengig av forretningsdomenet ditt kan det være et en til mange-, en mange-til-en- eller mange til mange-forhold. Men når det kommer til din gjennomsnittlige, lille e-handelsbutikk, vil én ordre kun resultere i én faktura og omvendt.
  • Bestillinger og varer: Mange til mange.
  • Varer og kategorier: Mange til en. Igjen, dette er ikke slik på store e-handelssider, men vi har en liten operasjon.
  • Kategorier og underkategorier: én til mange. Igjen, du vil finne de fleste eksempler fra den virkelige verden som motsier dette, men hei, Eloquent er vanskelig nok som det er, så la oss ikke gjøre datamodelleringen vanskeligere!
  • Bestillinger og transaksjoner: En til mange. Jeg vil også legge til disse to punktene som en begrunnelse for valget mitt: 1) Vi kunne også ha lagt til et forhold mellom transaksjoner og fakturaer. Det er bare en beslutning om datamodellering. 2) Hvorfor en til mange her? Vel, det er vanlig at en ordrebetaling mislykkes av en eller annen grunn og lykkes neste gang. I dette tilfellet har vi to transaksjoner opprettet for den bestillingen. Om vi ​​ønsker å vise disse mislykkede transaksjonene eller ikke er en forretningsavgjørelse, men det er alltid en god idé å fange opp verdifulle data.
  Hvordan lage en rullegardinliste i Google Sheets

Er det andre forhold? Vel, mange flere forhold er mulige, men de er ikke praktiske. For eksempel kan vi si at en bruker har mange transaksjoner, så det bør være et forhold mellom dem. Det man må innse her er at det allerede er et indirekte forhold: brukere -> bestillinger -> transaksjoner, og generelt sett er det bra nok ettersom RDBMS er beist i å slå seg sammen i tabeller. For det andre vil å opprette denne relasjonen bety å legge til en user_id-kolonne i transaksjonstabellen. Hvis vi gjorde dette for alle mulige direkte relasjoner, ville vi lagt til mye mer belastning på databasen (i form av mer lagring, spesielt hvis UUID-er brukes, og vedlikeholde indekser), lenke ned det generelle systemet. Jada, hvis virksomheten sier at de trenger transaksjonsdata og trenger det innen 1,5 sekunder, kan vi bestemme oss for å legge til det forholdet og få fart på sakene (tradeoffs, tradeoffs…).

Og nå, mine damer og herrer, er tiden inne for å skrive selve koden!

Laravel-modellrelasjoner – ekte eksempel med kode

Den neste fasen av denne artikkelen handler om å skitne hendene våre – men på en nyttig måte. Vi henter de samme databaseenhetene som i det tidligere e-handelseksemplet, og vi vil se hvordan modeller i Laravel lages og kobles sammen, rett fra installasjon av Laravel!

Naturligvis antar jeg at du har satt opp utviklingsmiljøet ditt og at du vet hvordan du installerer og bruker Composer for å administrere avhengigheter.

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

Disse to konsollkommandoene installerer Laravel-installasjonsprogrammet (-W-delen brukes til oppgradering siden jeg allerede hadde en eldre versjon installert). Og i tilfelle du er nysgjerrig, i skrivende stund er Laravel-versjonen som ble installert 8.5.9. Bør du få panikk og oppgradere også? Jeg vil fraråde det, siden jeg ikke forventer noen store endringer mellom Laravel 5 og Laravel 8 i forbindelse med søknaden vår. Noen ting har endret seg og vil påvirke denne artikkelen (som Model Fabrikker), men jeg tror du vil kunne portere koden.

Siden vi allerede har tenkt gjennom datamodellen og deres relasjoner, vil delen av å lage modellene være triviell. Og du vil også se (jeg høres ut som en ødelagt rekord nå!) hvordan det speiler databaseskjemaet ettersom det er 100% avhengig av det!

Med andre ord må vi først lage migreringene (og modellfilene) for alle modellene, som skal brukes på databasen. Senere kan vi jobbe med modellene og ta tak i relasjonene.

Så hvilken modell begynner vi med? Den enkleste og minst tilkoblede, selvfølgelig. I vårt tilfelle betyr dette brukermodellen. Siden Laravel leveres med denne modellen (og ikke kan fungere uten den 🤣), la oss endre migrasjonsfilen og også rydde opp i modellen for å passe våre enkle behov.

Her er migrasjonsklassen:

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

Siden vi faktisk ikke bygger et prosjekt, trenger vi ikke gå inn på passord, is_active og alt det der. Vår brukertabell vil bare ha to kolonner: ID og navnet på brukeren.

La oss lage migreringen for kategori neste. Siden Laravel gir oss muligheten til å generere modellen også i en enkelt kommando, vil vi dra nytte av det, selv om vi ikke berører modellfilen foreløpig.

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

Og her er migrasjonsklassen:

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

Hvis du er overrasket over fraværet av down()-funksjonen, ikke vær; i praksis ender du sjelden opp med å bruke det som å slippe en kolonne eller tabell eller endre en kolonnetype resulterer i tap av data som ikke kan gjenopprettes. Under utvikling vil du oppdage at du slipper hele databasen og deretter kjører migreringene på nytt. Men vi går bort, så la oss komme tilbake og takle den neste enheten. Siden underkategorier er direkte relatert til kategorier, tror jeg det er en god idé å gjøre det neste gang.

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

Greit, la oss nå fylle opp 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, legger vi til en egen kolonne her, kalt kategori_id, som vil lagre IDer fra kategoritabellen. Ingen premier for å gjette, dette skaper en til mange relasjoner på databasenivå.

Nå er det gjenstandens tur:

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

Og migrasjonen:

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');
        });
    }
}

Hvis du føler at ting bør gjøres annerledes, er det greit. To personer vil sjelden komme opp med nøyaktig samme skjema og arkitektur. Legg merke til én ting, som er en slags beste praksis: Jeg har lagret prisen som et heltall.

Hvorfor?

Vel, folk skjønte at håndtering av flytedivisjoner og alt var stygg og feilutsatt på databasesiden, så de begynte å lagre prisen i form av den minste valutaenheten. Hvis vi for eksempel opererte i USD, ville prisfeltet her representert cent. Gjennom hele systemet vil verdiene og beregningene være i cent; først når det er på tide å vise til brukeren eller sende en PDF på e-post, deler vi på 100 og runder av. Smart, ikke sant?

Uansett, legg merke til at et element er knyttet til en underkategori i et mange-til-en-forhold. Den er også knyttet til en kategori . . . indirekte via sin underkategori. Vi vil se solide demonstrasjoner av all denne gymnastikken, men foreløpig må vi sette pris på konseptene og sørge for at vi er 100 % klare.

Neste opp er ordremodellen og dens migrering:

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

For korthets skyld vil jeg bare inkludere noen av de viktige feltene i migrasjonen. Med det mener jeg, en ordres detaljer kan inneholde veldig mange ting, men vi vil begrense dem til noen få slik at vi kan fokusere på konseptet med modellforhold.

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 vent litt! Hvor er varene i denne bestillingen? Som vi etablerte tidligere, er det et mange-til-mange forhold mellom bestillinger og varer, så en enkel fremmednøkkel fungerer ikke. Løsningen er et såkalt skjøtebord eller mellombord. Vi trenger med andre ord et sammenføyningsbord for å lagre mange-til-mange-kartleggingen mellom bestillinger og varer. Nå, i Laravel-verdenen, er det en innebygd konvensjon som vi følger for å spare tid: Hvis jeg lager en ny tabell ved å bruke entallsformen til de to tabellnavnene, plasserer du dem i ordbokrekkefølge og slår dem sammen med et understrek, Laravel vil automatisk gjenkjenne det som sammenføyningsbordet.

I vårt tilfelle vil sammenføyningstabellen bli kalt item_order (ordet «item» kommer før «ordre» i en ordbok). Også, som forklart før, vil denne sammenføyningstabellen normalt bare inneholde to kolonner, fremmednøkler til hver tabell.

Vi kunne laget en modell + migrering her, men modellen vil aldri bli brukt da den er mer en meta-ting. Dermed oppretter vi en ny migrasjon i Laravel og forteller den hva som er hva.

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

Dette resulterer i en ny migrering, som vi vil endre som følger:

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');    
        });
    }
}

Hvordan man faktisk får tilgang til disse relasjonene gjennom veltalende metodeanrop er et emne for senere, men legg merke til at vi først møysommelig, for hånd, må lage disse fremmednøklene. Uten disse er det ingen veltalende og det er ingen «smart» i Laravel. 🙂

Er vi der ennå? Vel, nesten. . .

Vi har bare et par modeller til å bekymre seg for. Den første er fakturatabellen, og du vil huske at vi bestemte oss for å gjøre det til et en-til-en-forhold med bestillinger.

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

I de tidlige delene av denne artikkelen så vi at en måte å håndheve en en-til-en-relasjon på er å gjøre primærnøkkelen på barnetabellen til fremmednøkkelen også. I praksis er det knapt noen som tar denne altfor forsiktige tilnærmingen, og folk utformer generelt skjemaet slik de ville gjort for et en-til-mange forhold. Min oppfatning er at en middels tilnærming er bedre; bare gjør fremmednøkkelen unik, og du har sørget for at den overordnede modellens ID-er ikke kan gjentas:

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();
        });
    }
}

Og ja, for femtende gang er jeg klar over at denne fakturatabellen mangler mye; vårt fokus her er imidlertid å se hvordan modellrelasjoner fungerer og ikke å designe en hel database.

Ok, så vi har nådd det punktet hvor vi trenger å lage den endelige migreringen av systemet vårt (håper jeg!). Fokuset er nå på Transaksjonsmodellen, som vi tidligere bestemte er knyttet til Ordremodellen. Her er forresten en øvelse til deg: Bør Transaksjonsmodellen i stedet knyttes til Fakturamodellen? Hvorfor og hvorfor ikke? 🙂

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

Og migrasjonen:

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 litt hardt arbeid. . . la oss kjøre migreringene og se hvordan vi gjør det i databasens øyne.

$ 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)

Lovet være Herren! 🙏🏻🙏🏻 Det ser ut til at vi har overlevd prøvetiden.

Og med det er vi klare til å gå videre til å definere modellforhold! For det må vi gå tilbake til listen vi opprettet tidligere, og skissere typen direkte relasjoner mellom modeller (tabeller).

Til å begynne med har vi slått fast at det er et en-til-mange forhold mellom brukere og bestillinger. Vi kan bekrefte dette ved å gå til ordrenes migreringsfil og se tilstedeværelsen av feltet user_id der. Dette feltet er det som skaper forholdet, fordi ethvert forhold vi er interessert i å etablere, må respekteres av databasen først; resten (veltalende syntaks og hvor man skal skrive hvilken funksjon) er bare ren formalitet.

Forholdet er med andre ord allerede der. Vi trenger bare å fortelle Eloquent om å gjøre den tilgjengelig under kjøring. La oss starte med bestillingsmodellen, hvor vi erklærer at den tilhører brukermodellen:

<?php

namespace AppModels;

use IlluminateDatabaseEloquentFactoriesHasFactory;
use IlluminateDatabaseEloquentModel;

class Order extends Model
{
    use HasFactory;

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

Syntaksen må være kjent for deg; vi erklærer en funksjon som heter user(), som tjener til å få tilgang til brukeren som eier denne ordren (funksjonsnavnet kan være hva som helst; det er hva det returnerer som betyr noe). Tenk tilbake et øyeblikk – hvis det ikke var noen database og ingen fremmednøkler, ville en uttalelse som $this->belongsTo vært meningsløs. Det er bare fordi det er en fremmednøkkel på ordretabellen at Laravel kan bruke den user_id for å slå opp brukeren med samme id og returnere den. I seg selv, uten samarbeidet med databasen, kan ikke Laravel skape relasjoner ut av løse luften.

  Hvordan finne ekstern IP til Google Cloud VM?

Nå ville det også vært fint å kunne skrive $user->ordre for å få tilgang til en brukers bestillinger. Dette betyr at vi må gå til brukermodellen og skrive ut en funksjon for «mange»-delen av dette en-til-mange forholdet:

<?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, jeg har kraftig modifisert standard brukermodell fordi vi ikke trenger all annen funksjonalitet for denne opplæringen. Uansett, User-klassen har nå en metode som heter orders(), som sier at én bruker kan assosieres med flere ordrer. I ORM-verdenen sier vi at ordre()-relasjonen her er den inverse av bruker()-relasjonen vi hadde på Order-modellen.

Men, vent litt! Hvordan fungerer dette forholdet? Jeg mener, det er ingenting på databasenivå som har flere forbindelser som går ut fra brukertabellen til ordretabellen.

Faktisk er det en eksisterende tilkobling, og det viser seg at det er nok alene – fremmednøkkelreferansen lagret i ordretabellen! Dette vil si at når vi sier noe som $user->orders, treffer Laravel funksjonen orders() og vet ved å se på den at det er en fremmednøkkel på ordretabellen. Deretter utfører den en SELECT * FROM-ordre WHERE user_id = 23 og returnerer søkeresultatene som en samling. Hele poenget med å ha en ORM er selvfølgelig å glemme SQL, men vi bør ikke helt glemme at den underliggende basen er RDBMS som kjører SQL-spørringer.

La oss deretter bla gjennom ordre- og fakturamodellene, der vi har et en-til-en-forhold:

<?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);
    }
}

Og 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);
    }
}

Legg merke til at på databasenivå, så vel som nesten på veltalende nivå, er det et typisk en-til-mange forhold; vi har nettopp lagt til noen kontroller for å sikre at den forblir én-til-én.

Vi kommer nå til en annen type forhold – mange-til-mange mellom bestillinger og varer. Husk at vi allerede har laget en mellomtabell kalt item_order som lagrer tilordningen mellom primærnøklene. Hvis så mye er gjort riktig, er det trivielt å definere forholdet og jobbe med det. I henhold til Laravel-dokumentene, for å definere et mange-til-mange-forhold, må metodene dine returnere en belongsToMany()-forekomst.

Så i varemodellen:

<?php

namespace AppModels;

use IlluminateDatabaseEloquentFactoriesHasFactory;
use IlluminateDatabaseEloquentModel;

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

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

Overraskende nok er det omvendte forholdet nesten identisk:

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

Og det er det! Så lenge vi har fulgt navnekonvensjonene riktig, er Laravel i stand til å utlede kartleggingene så vel som deres plassering.

Siden alle de tre grunnleggende typene forhold har blitt dekket (en-til-en, en-til-mange, mange-til-mange), vil jeg slutte å skrive ut metodene for andre modeller, ettersom de vil være langs samme linjer. La oss i stedet lage fabrikkene for disse modellene, lage noen dummy-data og se disse relasjonene i aksjon!

Hvordan gjør vi det? Vel, la oss ta den raske og skitne banen og kaste alt inn i standard seeder-filen. Så, når vi kjører migreringene, kjører vi også seederen. Så, her er hvordan DatabaseSeeder.php-filen min ser 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,
        ]);
    }
}

Og nå setter vi opp databasen igjen og 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.

Greit! Nå er den siste delen av denne artikkelen, hvor vi bare får tilgang til disse relasjonene og bekrefter alt vi har lært så langt. Du vil bli glad for å vite (håper jeg) at dette blir en lett og morsom seksjon.

Og nå, la oss fyre opp den morsomste Laravel-komponenten – den interaktive Tinker-konsollen!

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

Få tilgang til en-til-en modellforhold i Laravel Eloquent

Ok, så la oss først få tilgang til en-til-en-forholdet vi har i våre ordre- og 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,
   }

Legg merke til noe? Husk at slik det har blitt gjort på databasenivå, er dette forholdet en-til-mange, hvis ikke for de ekstra begrensningene. Så Laravel kunne ha returnert en samling av objekter (eller bare ett objekt) som resultat, og det ville vært teknisk nøyaktig. MEN . . . vi har fortalt Laravel at det er et en-til-en forhold, så resultatet er en enkelt veltalende forekomst. Legg merke til hvordan det samme skjer når du får tilgang til det omvendte forholdet:

$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å tilgang til en-til-mange modellforhold i Laravel Eloquent

Vi har et en-til-mange forhold mellom brukere og bestillinger. La oss «tulle» med det nå og se utgangen:

>>> 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",
   }

Nøyaktig som forventet resulterer tilgang til en brukers bestillinger i en samling poster, mens det omvendte produserer bare ett enkelt brukerobjekt. Med andre ord en-til-mange.

Få tilgang til mange-til-mange modellforhold i Laravel Eloquent

La oss nå utforske et forhold som er mange-til-mange. Vi har et slikt forhold mellom varer og bestillinger:

>>> $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,
         },
       },
     ],
   }

Denne utgangen kan være litt svimlende å lese gjennom, men legg merke til at item1 er en del av ordre1s varer, og omvendt, som er hvordan vi setter opp ting. La oss også kikke inn i mellomtabellen som lagrer tilordningene:

>>> 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,
       },
     ],
   }

Konklusjon

Ja, dette er det, virkelig! Det har vært en veldig lang artikkel, men jeg håper den har vært nyttig. Er det alt man trenger å vite om Laravel-modeller?

Dessverre, nei. Kaninhullet er virkelig, virkelig dypt, og det er mange mer utfordrende konsepter som Polymorphic Relationships og ytelsesjustering, og hva ikke, som du vil møte når du vokser som Laravel-utvikler. Foreløpig er det som denne artikkelen dekker nok for 70 % av utviklerne 70 % av tiden, grovt sett. Det vil ta veldig lang tid før du føler behov for å oppgradere kunnskapen din.

Med det forbeholdet ute av veien, vil jeg at du skal ta bort denne viktigste innsikten: ingenting er mørk magi eller utenfor rekkevidde i programmering. Det er bare det at vi ikke forstår grunnlaget og hvordan ting er bygget, noe som gjør at vi sliter og føler oss frustrerte.

Så . . . ?

Invester i deg selv! Kurs, bøker, artikler, andre programmeringsfellesskap (Python er min #1 anbefaling) – bruk de ressursene du kan finne og bruk dem regelmessig hvis det går sakte. Ganske snart vil antallet tilfeller der du sannsynligvis feiler hele greia redusere drastisk.

Ok, nok forkynnelse. Ha en fin dag! 🙂