Modellbeziehungen in Laravel Eloquent verstehen

Models und ihre Beziehungen sind das Herzstück von Laravel Eloquent. Wenn sie Ihnen Schwierigkeiten bereiten oder Sie keine einfache, freundliche und vollständige Anleitung finden können, fangen Sie hier an!

Auf der anderen Seite ihres Programmierartikels sitzend, ist es für den Autor einfach, die Aura von Fachwissen/Prestige, die die Plattform bietet, vorzutäuschen oder zu sprengen. Aber ich bin ehrlich – ich hatte eine extrem harte Zeit Laravel lernen, schon allein, weil es mein erstes Full-Stack-Framework war. Ein Grund war, dass ich es nicht bei der Arbeit benutzte und es aus Neugier erforschte; Also würde ich einen Versuch machen, zu einem Punkt kommen, verwirrt werden, aufgeben und schließlich alles vergessen. Ich muss das 5-6 Mal gemacht haben, bevor es für mich Sinn machte (natürlich hilft die Dokumentation nicht).

Aber was immer noch keinen Sinn ergab, war Eloquent. Oder zumindest die Beziehungen zwischen Modellen (weil Eloquent zu groß ist, um vollständig zu lernen). Beispiele von Modellierungsautoren und Blogposts sind ein Witz, weil echte Projekte viel komplexer sind; Leider verwenden die offiziellen Dokumente dieselben (oder ähnliche) Beispiele. Oder selbst wenn ich auf einen nützlichen Artikel/eine nützliche Ressource gestoßen bin, war die Erklärung so schlecht oder fehlte so sehr, dass es einfach keinen Sinn machte.

(Übrigens wurde ich schon einmal angegriffen, weil ich die offizielle Dokumentation angegriffen habe. Wenn Sie also ähnliche Ideen haben, hier ist meine Standardantwort: Sehen Sie sich die Django-Dokumentation an und sprechen Sie dann mit mir.)

Irgendwann fügte es sich Stück für Stück zusammen und ergab Sinn. Endlich konnte ich Projekte richtig modellieren und die Modelle komfortabel nutzen. Dann stieß ich eines Tages auf ein paar nette Collections-Tricks, die diese Arbeit angenehmer machen. In diesem Artikel möchte ich alles abdecken, angefangen bei den Grundlagen bis hin zu allen möglichen Anwendungsfällen, denen Sie in realen Projekten begegnen werden.

Warum sind eloquente Modelbeziehungen schwierig?

Leider stoße ich auf viel zu viele Laravel-Entwickler, die Modelle nicht richtig verstehen.

Aber wieso?

Selbst heute, wo es eine Explosion von Kursen, Artikeln und Videos zu Laravel gibt, ist das Gesamtverständnis schlecht. Ich denke, das ist ein wichtiger Punkt und es lohnt sich, darüber nachzudenken.

Wenn Sie mich fragen, würde ich sagen, dass Eloquent Model-Beziehungen überhaupt nicht schwer sind. Zumindest wenn man es aus der Perspektive der Definition von „hart“ betrachtet. Live-Schemamigrationen sind schwierig; das Schreiben einer neuen Templating-Engine ist schwierig; Code zum Kern von Laravel beizutragen ist schwierig. Im Vergleich dazu das Erlernen und Verwenden eines ORM . . . Nun, das kann nicht schwer sein! 🤭🤭

Was tatsächlich passiert, ist, dass PHP-Entwickler, die Laravel lernen, Eloquent schwer finden. Das ist das eigentlich zugrunde liegende Problem, und meiner Meinung nach gibt es mehrere Faktoren, die dazu beitragen (harsche, unpopuläre Meinungswarnung!):

  • Vor Laravel war CodeIgniter der Kontakt zu einem Framework für die meisten PHP-Entwickler (es ist immer noch am Leben, übrigens, auch wenn es Laravel/CakePHP-ähnlicher geworden ist). In der älteren CodeIgniter-Community (falls es eine gab) bestand die „Best Practice“ darin, SQL-Abfragen direkt dort einzufügen, wo sie benötigt wurden. Und obwohl wir heute einen neuen CodeIgniter haben, haben sich die Gewohnheiten übernommen. Daher ist beim Erlernen von Laravel die Idee eines ORM für PHP-Entwickler zu 100 % neu.
  • Abgesehen von dem sehr kleinen Prozentsatz von PHP, der Frameworks wie Yii, CakePHP usw. ausgesetzt ist, sind die verbleibenden daran gewöhnt, in Kern-PHP oder in einer Umgebung wie WordPress zu arbeiten. Und auch hier gibt es kein OOP-basiertes Mindset, also ein Framework, einen Service-Container, ein Design-Pattern, ein ORM . . . das sind fremde Konzepte.
  • In der PHP-Welt gibt es wenig bis gar kein Konzept des kontinuierlichen Lernens. Der durchschnittliche Entwickler arbeitet gerne mit Einzelserver-Setups, die relationale Datenbanken verwenden und als Zeichenfolgen geschriebene Abfragen ausgeben. Asynchrone Programmierung, Web-Sockets, HTTP 2/3, Linux (vergessen Sie Docker), Unit-Tests, Domain-Driven Design – das sind alles Fremdwörter für einen überwältigenden Anteil von PHP-Entwicklern. Infolgedessen findet das Lesen von etwas Neuem und Herausforderndem bis zu dem Punkt, an dem man es als angenehm empfindet, nicht statt, wenn Eloquent begegnet.
  • Das allgemeine Verständnis von Datenbanken und Modellierung ist ebenfalls schlecht. Da Datenbankdesign direkt und untrennbar mit Eloquent-Modellen verbunden ist, legt es die Messlatte höher.

Ich will nicht hart sein und global verallgemeinern – es gibt auch exzellente PHP-Entwickler, und viele von ihnen, aber ihr Gesamtprozentsatz ist sehr gering.

Wenn Sie dies lesen, bedeutet das, dass Sie all diese Barrieren überwunden haben, auf Laravel gestoßen sind und sich mit Eloquent angelegt haben.

Herzliche Glückwünsche! 👏

Du bist fast da. Alle Bausteine ​​sind vorhanden und wir müssen sie nur in der richtigen Reihenfolge und im Detail durchgehen. Mit anderen Worten, beginnen wir auf der Datenbankebene.

Datenbankmodelle: Beziehungen und Kardinalität

Nehmen wir der Einfachheit halber an, dass wir in diesem Artikel nur mit relationalen Datenbanken arbeiten. Ein Grund dafür ist, dass ORMs ursprünglich für relationale Datenbanken entwickelt wurden; Der andere Grund ist, dass RDBMS immer noch überwältigend beliebt sind.

Datenmodell

Lassen Sie uns zunächst Datenmodelle besser verstehen. Die Idee eines Modells (genauer gesagt eines Datenmodells) kommt aus der Datenbank. Keine Datenbank, keine Daten, also kein Datenmodell. Und was ist ein Datenmodell? Ganz einfach, es ist die Art und Weise, wie Sie Ihre Daten speichern/strukturieren. Beispielsweise könnten Sie in einem E-Commerce-Shop alles in einer riesigen Tabelle speichern (SCHRECKLICHE Praxis, aber leider nicht ungewöhnlich in der PHP-Welt); das wäre Ihr Datenmodell. Sie können die Daten auch in 20 Haupt- und 16 Verbindungstabellen aufteilen; das ist auch ein Datenmodell.

Beachten Sie auch, dass die Art und Weise, wie Daten in der Datenbank strukturiert sind, nicht zu 100 % mit der Anordnung im ORM des Frameworks übereinstimmen muss. Die Bemühungen sind jedoch immer, die Dinge so nah wie möglich zu halten, damit wir bei der Entwicklung nichts mehr beachten müssen.

Kardinalität

Lassen Sie uns auch diesen Begriff schnell aus dem Weg räumen: Kardinalität. Es bezieht sich nur auf „Zählen“, grob gesagt. Also 1, 2, 3. . . können alle die Kardinalität von etwas sein. Ende der Geschichte. Lass uns weitermachen!

Beziehungen

Wann immer wir Daten in einem beliebigen System speichern, gibt es Möglichkeiten, Datenpunkte miteinander in Beziehung zu setzen. Ich weiß, das klingt abstrakt und langweilig, aber ertragen Sie mich ein wenig. Die Art und Weise, wie verschiedene Datenelemente verbunden sind, wird als Beziehungen bezeichnet. Sehen wir uns zuerst einige Nicht-Datenbank-Beispiele an, damit wir überzeugt sind, dass wir die Idee vollständig verstehen.

  • Wenn wir alles in einem Array speichern, ist eine mögliche Beziehung: Das nächste Datenelement befindet sich an einem Index, der um 1 größer ist als der vorherige Index.
  • Wenn wir Daten in einem Binärbaum speichern, besteht eine mögliche Beziehung darin, dass der untergeordnete Baum auf der linken Seite immer kleinere Werte hat als der übergeordnete Knoten (wenn wir uns dafür entscheiden, den Baum auf diese Weise zu pflegen).
  • Wenn wir Daten als Array von Arrays gleicher Länge speichern, können wir eine Matrix nachahmen, und dann werden ihre Eigenschaften zu den Beziehungen für unsere Daten.

Wir sehen also, dass das Wort „Beziehung“ im Zusammenhang mit Daten keine feste Bedeutung hat. Wenn zwei Personen dieselben Daten betrachten, könnten sie sogar zwei sehr unterschiedliche Datenbeziehungen identifizieren (Hallo, Statistik!), und beide könnten gültig sein.

Relationale Datenbanken

Basierend auf all den Begriffen, die wir bisher diskutiert haben, können wir endlich über etwas sprechen, das eine direkte Verbindung zu Modellen in einem Web-Framework (Laravel) hat – relationale Datenbanken. Für die meisten von uns ist die primär verwendete Datenbank MySQL, MariaDB, PostgreSQL, MSSQL, SQL Server, SQLite oder etwas Ähnliches. Wir wissen vielleicht auch vage, dass diese RDBMS genannt werden, aber die meisten von uns haben vergessen, was es eigentlich bedeutet und warum es wichtig ist.

Das „R“ in RDBMS steht natürlich für Relational. Dies ist kein willkürlich gewählter Begriff; Damit heben wir die Tatsache hervor, dass diese Datenbanksysteme so konzipiert sind, dass sie effizient mit Beziehungen zwischen gespeicherten Daten arbeiten. Tatsächlich hat „Beziehung“ hier eine streng mathematische Bedeutung, und obwohl sich kein Entwickler darum kümmern muss, ist es hilfreich zu wissen, dass es eine strenge Mathematik gibt Stiftung unterhalb dieser Arten von Datenbanken.

Erkunden Sie diese Ressourcen, um SQL und NoSQL zu lernen.

Okay, wir wissen also aus Erfahrung, dass Daten in RDBMS als Tabellen gespeichert werden. Wo sind dann die Beziehungen?

Arten von Beziehungen in RDBMS

Dies ist vielleicht der wichtigste Teil des gesamten Themas Laravel und Modellbeziehungen. Wenn Sie das nicht verstehen, wird Eloquent niemals Sinn machen, also passen Sie bitte die nächsten paar Minuten auf (es ist nicht einmal so schwierig).

Ein RDBMS ermöglicht es uns, Beziehungen zwischen Daten herzustellen – auf Datenbankebene. Dies bedeutet, dass diese Beziehungen nicht unpraktisch/imaginär/subjektiv sind und von verschiedenen Personen mit demselben Ergebnis erstellt oder abgeleitet werden können.

Gleichzeitig gibt es bestimmte Funktionen/Tools innerhalb eines RDBMS, die es uns ermöglichen, diese Beziehungen zu erstellen und durchzusetzen, wie zum Beispiel:

  • Primärschlüssel
  • Unbekannter Schlüssel
  • Einschränkungen

Ich möchte nicht, dass dieser Artikel zu einem Datenbankkurs wird, also gehe ich davon aus, dass Sie diese Konzepte kennen. Wenn nicht, oder falls Sie sich in Ihrem Selbstvertrauen unsicher fühlen, empfehle ich dieses freundliche Video (Sie können sich gerne die gesamte Serie ansehen):

Zufällig sind diese Beziehungen im RDBMS-Stil auch die häufigsten, die in realen Anwendungen auftreten (nicht immer, da ein soziales Netzwerk am besten als Diagramm und nicht als Sammlung von Tabellen modelliert wird). Schauen wir sie uns also nacheinander an und versuchen wir auch zu verstehen, wo sie nützlich sein könnten.

Eins-zu-eins-Beziehung

In fast jeder Webanwendung gibt es Benutzerkonten. Außerdem gilt Folgendes (allgemein gesprochen) für die Benutzer und Konten:

  • Ein Benutzer kann nur ein Konto haben.
  • Ein Konto kann nur einem Benutzer gehören.

Ja, wir können argumentieren, dass sich eine Person mit einer anderen E-Mail-Adresse anmelden und somit zwei Konten erstellen kann, aber aus Sicht der Webanwendung sind dies zwei verschiedene Personen mit zwei verschiedenen Konten. Die Anwendung zeigt beispielsweise nicht die Daten eines Kontos in einem anderen an.

Was all diese Haarspalterei bedeutet: Wenn Sie in Ihrer Anwendung eine Situation wie diese haben und eine relationale Datenbank verwenden, müssen Sie sie als Eins-zu-eins-Beziehung entwerfen. Beachten Sie, dass Sie niemand künstlich dazu zwingt – es gibt eine klare Situation in der Geschäftswelt und Sie verwenden zufällig eine relationale Datenbank . . . Nur wenn diese beiden Bedingungen erfüllt sind, erreichen Sie eine Eins-zu-Eins-Beziehung.

Für dieses Beispiel (Benutzer und Konten) können wir diese Beziehung beim Erstellen des Schemas folgendermaßen implementieren:

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

Beachten Sie den Trick hier? Es ist ziemlich ungewöhnlich beim Erstellen von Apps im Allgemeinen, aber in der Kontentabelle haben wir die Feld-ID sowohl als Primärschlüssel als auch als Fremdschlüssel festgelegt! Die Fremdschlüsseleigenschaft verknüpft sie mit der Benutzertabelle (natürlich 🙄), während die Primärschlüsseleigenschaft die ID-Spalte eindeutig macht – eine echte Eins-zu-Eins-Beziehung!

Zugegeben, die Treue dieser Beziehung ist nicht garantiert. Zum Beispiel hindert mich nichts daran, 200 neue Benutzer hinzuzufügen, ohne einen einzigen Eintrag zur Kontentabelle hinzuzufügen. Wenn ich das tue, habe ich am Ende eine Eins-zu-Null-Beziehung! 🤭🤭 Aber innerhalb der Grenzen der reinen Struktur ist das das Beste, was wir tun können. Wenn wir verhindern wollen, dass Benutzer ohne Konten hinzugefügt werden, müssen wir uns von einer Art Programmierlogik helfen lassen, entweder in Form von Datenbankauslösern oder von Laravel erzwungenen Validierungen.

  Wie benenne ich den Weblogic-Domainnamen um?

Wenn Sie anfangen, sich zu stressen, habe ich einige sehr gute Ratschläge:

  • Mach langsam. So langsam wie nötig. Anstatt zu versuchen, diesen Artikel und die 15 anderen, für die Sie sich heute ein Lesezeichen gesetzt haben, fertigzustellen, bleiben Sie bei diesem. Lassen Sie es 3, 4, 5 Tage dauern, wenn es das ist, was es braucht – Ihr Ziel sollte es sein, Eloquent Model-Beziehungen für immer von Ihrer Liste zu streichen. Sie sind schon früher von Artikel zu Artikel gesprungen, haben mehrere hundert Stunden verschwendet und es hat trotzdem nicht geholfen. Also mach es diesmal anders. 😇
  • Während es in diesem Artikel um Laravel Eloquent geht, kommt das alles viel später. Die Grundlage von allem ist das Datenbankschema, also sollten wir uns zuerst darauf konzentrieren, das richtig zu machen. Wenn Sie nicht rein auf Datenbankebene arbeiten können (vorausgesetzt, es gibt keine Frameworks auf der Welt), dann werden Modelle und Beziehungen niemals wirklich sinnvoll sein. Also vergiss Laravel vorerst. Vollständig. Wir reden vorerst nur über Datenbankdesign und führen es durch. Ja, ich werde ab und zu auf Laravel verweisen, aber Ihre Aufgabe ist es, sie vollständig zu ignorieren, wenn sie das Bild für Sie verkomplizieren.
  • Lesen Sie später etwas mehr über Datenbanken und was sie bieten. Indizes, Leistung, Trigger, zugrunde liegende Datenstrukturen und deren Verhalten, Caching, Beziehungen in MongoDB . . . alle Randthemen, die Sie behandeln können, werden Ihnen als Ingenieur helfen. Denken Sie daran, dass Rahmenmodelle nur Geisterhüllen sind; Die eigentliche Funktionalität einer Plattform ergibt sich aus den zugrunde liegenden Datenbanken.

Eins-zu-viele-Beziehung

Ich bin mir nicht sicher, ob Sie das erkannt haben, aber das ist die Art von Beziehung, die wir alle intuitiv in unserer täglichen Arbeit herstellen. Wenn wir beispielsweise eine Auftragstabelle erstellen (ein hypothetisches Beispiel), um einen Fremdschlüssel für die Benutzertabelle zu speichern, erstellen wir eine Eins-zu-Viele-Beziehung zwischen Benutzern und Bestellungen. Warum ist das so? Betrachten Sie es noch einmal aus der Perspektive, wer wie viele haben darf: Ein Benutzer darf mehr als eine Bestellung haben, und so funktioniert der gesamte E-Commerce. Und von der anderen Seite gesehen besagt die Beziehung, dass eine Bestellung nur einem Nutzer gehören kann, was auch sehr viel Sinn macht.

In der Datenmodellierung, in RDBMS-Büchern und in der Systemdokumentation wird diese Situation schematisch wie folgt dargestellt:

Beachten Sie die drei Linien, die eine Art Dreizack bilden? Dies ist das Symbol für „viele“, und dieses Diagramm besagt, dass ein Benutzer viele Bestellungen haben kann.

Übrigens, diese „viele“ und „eins“ Zählungen, denen wir immer wieder begegnen, sind das, was man die Kardinalität einer Beziehung nennt (erinnern Sie sich an dieses Wort aus einem vorherigen Abschnitt?). Auch hier hat der Begriff für diesen Artikel keine Bedeutung, aber es hilft, das Konzept zu kennen, falls es in Interviews oder beim Weiterlesen auftaucht.

Einfach, oder? Und in Bezug auf das eigentliche SQL ist das Erstellen dieser Beziehung ebenfalls einfach. Tatsächlich ist es viel einfacher als bei einer Eins-zu-Eins-Beziehung!

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

Die Auftragstabelle speichert Benutzer-IDs für jeden Auftrag. Da es keine Einschränkung gibt, dass die Benutzer-IDs in der Auftragstabelle eindeutig sein müssen, bedeutet dies, dass wir eine einzelne ID viele Male wiederholen können. Dies ist es, was die Eins-zu-Viele-Beziehung schafft, und nicht irgendeine obskure Magie, die darunter verborgen ist. Die Benutzer-IDs werden irgendwie dumm in der Orders-Tabelle gespeichert, und SQL hat kein Konzept von One-to-Many, One-to-One usw. Aber sobald wir Daten auf diese Weise speichern, können wir kann sich vorstellen, dass es eine Eins-zu-Viele-Beziehung gibt.

Hoffentlich macht es jetzt Sinn. Oder zumindest sinnvoller als zuvor. 😅 Denken Sie daran, dass dies wie alles andere nur eine Frage der Übung ist, und wenn Sie dies 4-5 Mal in realen Situationen gemacht haben, werden Sie nicht einmal darüber nachdenken.

Viele-zu-viele-Beziehungen

Die nächste Art von Beziehung, die in der Praxis auftritt, ist die sogenannte Viele-zu-Viele-Beziehung. Noch einmal, bevor wir uns Gedanken über Frameworks machen oder sogar in Datenbanken eintauchen, lassen Sie uns an ein Analogon aus der realen Welt denken: Bücher und Autoren. Denken Sie an Ihren Lieblingsautor; Sie haben mehr als ein Buch geschrieben, richtig? Gleichzeitig ist es ziemlich üblich, dass mehrere Autoren an einem Buch zusammenarbeiten (zumindest im Sachbuchgenre). Ein Autor kann also viele Bücher schreiben, und viele Autoren können ein Buch schreiben. Zwischen den beiden Entitäten (Buch und Autor) bildet dies eine Viele-zu-Viele-Beziehung.

Nun, vorausgesetzt, Sie werden höchstwahrscheinlich keine reale App erstellen, die Bibliotheken oder Bücher und Autoren umfasst, also lassen Sie uns an einige weitere Beispiele denken. In einer B2B-Umgebung bestellt ein Hersteller Artikel bei einem Lieferanten und erhält im Gegenzug eine Rechnung. Die Rechnung enthält mehrere Posten, von denen jeder die gelieferte Menge und den gelieferten Artikel auflistet; B. 5-Zoll-Rohrstücke x 200 usw. In dieser Situation haben Artikel und Rechnungen eine Viele-zu-Viele-Beziehung (überlegen und sich selbst überzeugen). In einem Flottenmanagementsystem haben Fahrzeuge und Fahrer eine ähnliche Beziehung. Auf einer E-Commerce-Website können Benutzer und Produkte eine Viele-zu-Viele-Beziehung haben, wenn wir Funktionen wie Favoriten oder Wunschlisten berücksichtigen.

Fair genug, wie erstellt man nun diese Viele-zu-Viele-Beziehung in SQL? Basierend auf unserem Wissen darüber, wie die Eins-zu-Viele-Beziehung funktioniert, könnte es verlockend sein zu glauben, wir sollten Fremdschlüssel für die andere Tabelle in beiden Tabellen speichern. Wir stoßen jedoch auf große Probleme, wenn wir dies versuchen. Schauen Sie sich dieses Beispiel an, in dem Bücher von Autoren eine Viele-zu-Viele-Beziehung haben sollen:

Auf den ersten Blick sieht alles in Ordnung aus – Bücher werden den Autoren exakt in einer Many-to-Many-Manier zugeordnet. Aber schauen Sie sich die Daten der Autorentabelle genau an: Die Buch-IDs 12 und 13 wurden beide von Peter M. (Autoren-ID 2) geschrieben, weshalb wir keine andere Wahl haben, als die Einträge zu wiederholen. Die Tabelle authors hat jetzt nicht nur Datenintegritätsprobleme (proper Normalisierung und all das), wiederholen sich jetzt die Werte in der Spalte id. Das bedeutet, dass es in dem von uns gewählten Design keine Primärschlüsselspalte geben kann (weil Primärschlüssel keine doppelten Werte haben können) und alles auseinander fällt.

Natürlich brauchen wir dafür einen neuen Weg, und glücklicherweise wurde dieses Problem bereits gelöst. Da das Speichern von Fremdschlüsseln direkt in beiden Tabellen die Dinge vermasselt, ist der richtige Weg, viele-zu-viele-Beziehungen in RDBMS zu erstellen, das Erstellen einer sogenannten „Joining-Tabelle“. Die Idee ist im Grunde, die beiden ursprünglichen Tabellen ungestört stehen zu lassen und eine dritte Tabelle zu erstellen, um das Many-to-Many-Mapping zu demonstrieren.

Lassen Sie uns das fehlgeschlagene Beispiel wiederholen, um eine Joining-Tabelle zu enthalten:

Beachten Sie, dass es drastische Änderungen gegeben hat:

  • Die Anzahl der Spalten in der Autorentabelle wird reduziert.
  • Die Anzahl der Spalten in der Büchertabelle wird reduziert.
  • Die Anzahl der Zeilen in der Autorentabelle wird reduziert, da keine Wiederholung mehr erforderlich ist.
  • Eine neue Tabelle namens authors_books ist erschienen, die Informationen darüber enthält, welche Autoren-ID mit welcher Buch-ID verbunden ist. Wir hätten die Verknüpfungstabelle beliebig benennen können, aber per Konvention ist sie das Ergebnis der einfachen Verknüpfung der beiden Tabellen, die sie repräsentiert, mit einem Unterstrich.

Die Verknüpfungstabelle hat keinen Primärschlüssel und enthält in den meisten Fällen nur zwei Spalten – IDs aus den beiden Tabellen. Es ist fast so, als hätten wir die Fremdschlüsselspalten aus unserem vorherigen Beispiel entfernt und sie in diese neue Tabelle eingefügt. Da es keinen Primärschlüssel gibt, kann es so viele Wiederholungen geben, wie nötig sind, um alle Beziehungen aufzuzeichnen.

Nun können wir mit unseren Augen sehen, wie die Verbindungstabelle die Zusammenhänge übersichtlich darstellt, aber wie greifen wir in unseren Anwendungen darauf zu? Das Geheimnis ist mit dem Namen verknüpft – Joining-Tabelle. Dies ist kein Kurs über SQL-Abfragen, daher werde ich nicht darauf eingehen, aber die Idee ist, dass Sie, wenn Sie alle Bücher eines bestimmten Autors in einer effizienten Abfrage haben möchten, die Tabellen in derselben Reihenfolge per SQL verknüpfen –> Autoren, Autoren_Bücher und Bücher. Die Tabellen authors und authors_books werden über die Spalten id bzw. author_id verknüpft, während die Tabellen authors_books und books über die Spalten book_id bzw. id verknüpft werden.

Anstrengend, ja. Aber schauen Sie auf die gute Seite – wir haben alle notwendigen Theorien/Grundlagen erledigt, die wir erledigen mussten, bevor wir Eloquent-Modelle in Angriff nehmen konnten. Und ich möchte Sie daran erinnern, dass all diese Dinge nicht optional sind! Wenn Sie das Datenbankdesign nicht kennen, werden Sie für immer im Land der Verwirrung von Eloquent landen. Was auch immer Eloquent tut oder zu tun versucht, spiegelt diese Details auf Datenbankebene perfekt wider, sodass leicht einzusehen ist, warum der Versuch, Eloquent zu lernen, während man vor RDBMS davonläuft, eine vergebliche Übung ist.

Erstellen von Modellbeziehungen in Laravel Eloquent

Endlich, nach einem Umweg von etwa 70.000 Meilen, haben wir den Punkt erreicht, an dem wir über Eloquent, seine Modelle und deren Erstellung/Verwendung sprechen können. Nun, wir haben im vorherigen Teil des Artikels gelernt, dass alles mit der Datenbank beginnt und wie Sie Ihre Daten modellieren. Dadurch wurde mir klar, dass ich ein einzelnes, vollständiges Beispiel verwenden sollte, wenn ich ein neues Projekt beginne. Gleichzeitig möchte ich, dass dieses Beispiel real ist und nicht über Blogs und Autoren oder Bücher und Regale (die auch real sind, aber zu Tode gemacht wurden).

Stellen wir uns ein Geschäft vor, das Stofftiere verkauft. Nehmen wir außerdem an, dass wir das Anforderungsdokument erhalten haben, anhand dessen wir diese vier Entitäten im System identifizieren können: Benutzer, Bestellungen, Rechnungen, Artikel, Kategorien, Unterkategorien und Transaktionen. Ja, es wird wahrscheinlich mehr Komplikationen geben, aber lassen wir das einfach beiseite und konzentrieren uns darauf, wie wir von einem Dokument zu einer App gelangen.

Nachdem die Hauptentitäten im System identifiziert wurden, müssen wir darüber nachdenken, wie sie in Bezug auf die bisher besprochenen Datenbankbeziehungen zueinander in Beziehung stehen. Hier sind die, die mir einfallen:

  • Benutzer und Bestellungen: Einer zu vielen.
  • Bestellungen und Rechnungen: Eins zu eins. Mir ist klar, dass dies nicht ausgeschnitten und getrocknet ist, und abhängig von Ihrer Geschäftsdomäne kann es eine Eins-zu-Viele-, eine Viele-zu-Eins- oder eine Viele-zu-Viele-Beziehung geben. Aber in einem durchschnittlichen kleinen E-Commerce-Shop führt eine Bestellung nur zu einer Rechnung und umgekehrt.
  • Bestellungen und Artikel: Viele zu viele.
  • Artikel und Kategorien: Viele zu einem. Auch dies ist bei großen E-Commerce-Websites nicht der Fall, aber wir haben einen kleinen Betrieb.
  • Kategorien und Unterkategorien: eins bis viele. Auch hier finden Sie die meisten Beispiele aus der realen Welt, die dem widersprechen, aber hey, Eloquent ist so schon schwierig genug, also machen wir die Datenmodellierung nicht schwieriger!
  • Bestellungen und Transaktionen: One to Many. Ich möchte auch diese beiden Punkte als Begründung für meine Wahl hinzufügen: 1) Wir hätten auch eine Beziehung zwischen Transaktionen und Rechnungen hinzufügen können. Es ist nur eine Datenmodellierungsentscheidung. 2) Warum hier einer zu vielen? Nun, es ist üblich, dass eine Auftragszahlung aus irgendeinem Grund fehlschlägt und beim nächsten Mal erfolgreich ist. In diesem Fall haben wir zwei Transaktionen für diese Bestellung erstellt. Ob wir diese fehlgeschlagenen Transaktionen anzeigen möchten oder nicht, ist eine geschäftliche Entscheidung, aber es ist immer eine gute Idee, wertvolle Daten zu erfassen.
  So zeigen Sie alle gesperrten Nummern auf Ihrem iPhone an

Gibt es noch andere Beziehungen? Nun, viele weitere Beziehungen sind möglich, aber sie sind nicht praktikabel. Zum Beispiel können wir sagen, dass ein Benutzer viele Transaktionen hat, also sollte eine Beziehung zwischen ihnen bestehen. Hier muss man erkennen, dass es bereits eine indirekte Beziehung gibt: Benutzer -> Bestellungen -> Transaktionen, und im Allgemeinen ist es gut genug, da RDBMS Bestien beim Zusammenführen von Tabellen sind. Zweitens würde das Erstellen dieser Beziehung bedeuten, der Transaktionstabelle eine user_id-Spalte hinzuzufügen. Wenn wir dies für jede mögliche direkte Beziehung tun würden, würden wir der Datenbank viel mehr Last hinzufügen (in Form von mehr Speicherplatz, insbesondere wenn UUIDs verwendet werden, und Indizes pflegen) und das Gesamtsystem verketten. Sicher, wenn das Unternehmen sagt, dass es Transaktionsdaten benötigt und diese innerhalb von 1,5 Sekunden benötigt, können wir uns entscheiden, diese Beziehung hinzuzufügen und die Dinge zu beschleunigen (Kompromisse, Kompromisse …).

Und jetzt, meine Damen und Herren, ist es an der Zeit, den eigentlichen Code zu schreiben!

Laravel-Modellbeziehungen – echtes Beispiel mit Code

In der nächsten Phase dieses Artikels geht es darum, uns die Hände schmutzig zu machen – aber auf nützliche Weise. Wir werden die gleichen Datenbankentitäten wie im vorherigen E-Commerce-Beispiel aufgreifen und sehen, wie Modelle in Laravel erstellt und verbunden werden, direkt nach der Installation von Laravel!

Natürlich gehe ich davon aus, dass Sie Ihre Entwicklungsumgebung eingerichtet haben und wissen, wie Sie Composer installieren und verwenden, um Abhängigkeiten zu verwalten.

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

Diese beiden Konsolenbefehle installieren den Laravel-Installer (der -W-Teil wird für das Upgrade verwendet, da ich bereits eine ältere Version installiert hatte). Und falls Sie neugierig sind, ist die Laravel-Version, die zum Zeitpunkt des Schreibens installiert wurde, 8.5.9. Sollten Sie in Panik geraten und ebenfalls upgraden? Ich rate davon ab, da ich im Rahmen unserer Bewerbung keine großen Änderungen zwischen Laravel 5 und Laravel 8 erwarte. Einige Dinge haben sich geändert und werden sich auf diesen Artikel auswirken (z. B. Modellfabriken), aber ich denke, Sie können den Code portieren.

Da wir das Datenmodell und ihre Beziehungen bereits durchdacht haben, wird der Teil der Erstellung der Modelle trivial sein. Und Sie werden auch sehen (ich klinge jetzt wie eine kaputte Schallplatte!), wie es das Datenbankschema widerspiegelt, da es zu 100 % davon abhängig ist!

Mit anderen Worten, wir müssen zuerst die Migrationen (und Modelldateien) für alle Modelle erstellen, die auf die Datenbank angewendet werden. Später können wir an den Modellen arbeiten und die Beziehungen anheften.

Also, mit welchem ​​Modell fangen wir an? Die einfachste und am wenigsten zusammenhängende natürlich. In unserem Fall ist dies das Benutzermodell. Da Laravel mit diesem Modell ausgeliefert wird (und ohne es nicht funktionieren kann 🤣), ändern wir die Migrationsdatei und bereinigen auch das Modell, um es unseren einfachen Bedürfnissen anzupassen.

Hier ist die Migrationsklasse:

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

Da wir eigentlich kein Projekt erstellen, müssen wir uns nicht mit Passwörtern, is_active und all dem beschäftigen. Unsere Benutzertabelle wird nur zwei Spalten haben: die ID und den Namen des Benutzers.

Lassen Sie uns als Nächstes die Migration für die Kategorie erstellen. Da Laravel es uns ermöglicht, das Modell auch in einem einzigen Befehl zu generieren, werden wir davon profitieren, obwohl wir die Modelldatei vorerst nicht berühren werden.

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

Und hier ist die Migrationsklasse:

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

Wenn Sie das Fehlen der down()-Funktion überrascht, seien Sie das nicht; In der Praxis wird es selten verwendet, da das Löschen einer Spalte oder Tabelle oder das Ändern eines Spaltentyps zu Datenverlust führt, der nicht wiederhergestellt werden kann. In der Entwicklung werden Sie feststellen, dass Sie die gesamte Datenbank löschen und dann die Migrationen erneut ausführen. Aber wir schweifen ab, also lasst uns zurückkommen und die nächste Entität in Angriff nehmen. Da Unterkategorien in direktem Zusammenhang mit Kategorien stehen, halte ich es für eine gute Idee, dies als Nächstes zu tun.

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

In Ordnung, jetzt füllen wir die Migrationsdatei aus:

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

Wie Sie sehen können, fügen wir hier eine separate Spalte namens category_id hinzu, in der IDs aus der Kategorientabelle gespeichert werden. Keine Preise zum Raten, dies schafft eine Eins-zu-viele-Beziehung auf Datenbankebene.

Jetzt sind Items an der Reihe:

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

Und der Umzug:

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

Wenn Sie das Gefühl haben, dass die Dinge anders gemacht werden sollten, ist das in Ordnung. Zwei Personen kommen selten auf das exakt gleiche Schema und die gleiche Architektur. Beachten Sie eine Sache, die eine Art Best Practice ist: Ich habe den Preis als ganze Zahl gespeichert.

Wieso den?

Nun, die Leute erkannten, dass der Umgang mit Float-Divisionen und allem auf der Datenbankseite hässlich und fehleranfällig war, also fingen sie an, den Preis in Bezug auf die kleinste Währungseinheit zu speichern. Wenn wir beispielsweise in USD arbeiten, würde das Preisfeld hier Cent darstellen. Im gesamten System erfolgen die Werte und Berechnungen in Cent; Erst wenn es an der Zeit ist, dem Benutzer eine PDF-Datei anzuzeigen oder per E-Mail zu senden, teilen wir durch 100 und runden ab. Clever, nicht wahr?

Beachten Sie auf jeden Fall, dass ein Element in einer Viele-zu-Eins-Beziehung mit einer Unterkategorie verknüpft ist. Es ist auch mit einer Kategorie verknüpft. . . indirekt über seine Unterkategorie. Wir werden solide Demonstrationen all dieser Gymnastik sehen, aber jetzt müssen wir die Konzepte schätzen und sicherstellen, dass wir 100% klar sind.

Als nächstes kommt das Bestellmodell und seine Migration:

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

Der Kürze halber werde ich nur einige der wichtigen Felder in die Migration einbeziehen. Damit meine ich, dass die Details einer Bestellung sehr viele Dinge enthalten können, aber wir werden sie auf ein paar beschränken, damit wir uns auf das Konzept der Modellbeziehungen konzentrieren können.

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

Sieht gut aus, aber Moment mal! Wo sind die Artikel in dieser Bestellung? Wie wir bereits festgestellt haben, besteht zwischen Bestellungen und Artikeln eine Viele-zu-Viele-Beziehung, sodass ein einfacher Fremdschlüssel nicht funktioniert. Die Lösung ist ein sogenannter Fügetisch oder Zwischentisch. Mit anderen Worten, wir brauchen eine Verknüpfungstabelle, um die Viele-zu-Viele-Zuordnung zwischen Bestellungen und Artikeln zu speichern. Jetzt, in der Laravel-Welt, gibt es eine eingebaute Konvention, der wir folgen, um Zeit zu sparen: Wenn ich eine neue Tabelle erstelle, indem ich die Singularform der beiden Tabellennamen verwende, sie in der Wörterbuchreihenfolge platziere und sie mit einem Unterstrich verbinde, Laravel erkennt es automatisch als Verbindungstisch.

In unserem Fall wird die Verknüpfungstabelle item_order genannt (das Wort „item“ kommt in einem Wörterbuch vor „order“). Außerdem enthält diese Verknüpfungstabelle, wie zuvor erläutert, normalerweise nur zwei Spalten, Fremdschlüssel für jede Tabelle.

Wir könnten hier ein Modell + Migration erstellen, aber das Modell wird nie verwendet, da es eher eine Meta-Sache ist. Daher erstellen wir eine neue Migration in Laravel und sagen ihr, was was ist.

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

Daraus ergibt sich eine neue Migration, die wir wie folgt ändern werden:

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

Wie man tatsächlich über Eloquent-Methodenaufrufe auf diese Beziehungen zugreift, ist ein Thema für später, aber beachten Sie, dass wir diese Fremdschlüssel zuerst mühsam von Hand erstellen müssen. Ohne diese gibt es kein Eloquent und kein „Smart“ in Laravel. 🙂

Sind wir schon da? Naja fast . . .

Wir haben nur noch ein paar Modelle, um die wir uns Sorgen machen müssen. Die erste ist die Rechnungstabelle, und Sie werden sich daran erinnern, dass wir uns entschieden haben, eine Eins-zu-Eins-Beziehung mit Bestellungen zu erstellen.

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

In den sehr frühen Abschnitten dieses Artikels haben wir gesehen, dass eine Möglichkeit, eine Eins-zu-eins-Beziehung zu erzwingen, darin besteht, den Primärschlüssel der untergeordneten Tabelle auch zum Fremdschlüssel zu machen. In der Praxis verfolgt kaum jemand diesen übermäßig vorsichtigen Ansatz, und die Leute entwerfen das Schema im Allgemeinen so, wie sie es für eine Eins-zu-Viele-Beziehung tun würden. Meine Meinung ist, dass ein mittlerer Ansatz besser ist; Machen Sie einfach den Fremdschlüssel eindeutig und Sie haben sichergestellt, dass die IDs des übergeordneten Modells nicht wiederholt werden können:

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

Und ja, zum x-ten Mal ist mir bewusst, dass in dieser Rechnungstabelle einiges fehlt; Unser Fokus liegt hier jedoch darauf, zu sehen, wie Modellbeziehungen funktionieren, und nicht darauf, eine ganze Datenbank zu entwerfen.

Okay, wir haben also den Punkt erreicht, an dem wir die endgültige Migration unseres Systems erstellen müssen (hoffe ich!). Der Fokus liegt jetzt auf dem Transaktionsmodell, von dem wir zuvor entschieden haben, dass es mit dem Auftragsmodell verknüpft ist. Übrigens, hier eine Übung für Sie: Soll das Transaktionsmodell stattdessen mit dem Rechnungsmodell verknüpft werden? Warum und warum nicht? 🙂

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

Und der Umzug:

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! Das war harte Arbeit. . . Lassen Sie uns die Migrationen ausführen und sehen, wie wir in den Augen der Datenbank abschneiden.

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

Gepriesen sei der Herr! 🙏🏻🙏🏻 Sieht so aus, als hätten wir den Moment der Prüfung überstanden.

Und damit sind wir bereit, mit der Definition von Modellbeziehungen fortzufahren! Dazu müssen wir zu der Liste zurückkehren, die wir zuvor erstellt haben, und die Art der direkten Beziehungen zwischen Modellen (Tabellen) skizzieren.

Zunächst einmal haben wir festgestellt, dass zwischen Benutzern und Bestellungen eine Eins-zu-Viele-Beziehung besteht. Wir können dies bestätigen, indem wir zur Migrationsdatei der Bestellungen gehen und dort das Vorhandensein des Felds user_id sehen. Dieses Feld erstellt die Beziehung, da jede Beziehung, an der wir interessiert sind, zuerst von der Datenbank berücksichtigt werden muss; der Rest (eloquente Syntax und wo man welche Funktion schreibt) ist reine Formsache.

Mit anderen Worten, die Beziehung ist bereits da. Wir müssen Eloquent nur sagen, dass es zur Laufzeit verfügbar sein soll. Beginnen wir mit dem Order-Modell, wo wir erklären, dass es zum User-Modell gehört:

<?php

namespace AppModels;

use IlluminateDatabaseEloquentFactoriesHasFactory;
use IlluminateDatabaseEloquentModel;

class Order extends Model
{
    use HasFactory;

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

Die Syntax muss Ihnen vertraut sein; Wir deklarieren eine Funktion namens user(), die dazu dient, auf den Benutzer zuzugreifen, der diese Bestellung besitzt (der Funktionsname kann alles sein; es kommt darauf an, was er zurückgibt). Denken Sie noch einmal einen Moment zurück – wenn es keine Datenbank und keine Fremdschlüssel gäbe, wäre eine Anweisung wie $this->belongsTo bedeutungslos. Nur weil es einen Fremdschlüssel in der Orders-Tabelle gibt, kann Laravel diese user_id verwenden, um den Benutzer mit derselben ID zu suchen und zurückzugeben. Ohne die Zusammenarbeit mit der Datenbank kann Laravel keine Beziehungen aus dem Nichts herstellen.

  Die 12 besten Gaming-Monitore für hervorragende Leistung und geringe Eingangsverzögerung

Nun wäre es auch schön, $user->orders schreiben zu können, um auf die Bestellungen eines Benutzers zuzugreifen. Das bedeutet, dass wir zum Benutzermodell gehen und eine Funktion für den „vielen“ Teil dieser Eins-zu-Viele-Beziehung schreiben müssen:

<?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, ich habe das Standardbenutzermodell stark modifiziert, da wir nicht alle anderen Funktionen für dieses Tutorial benötigen. Wie auch immer, die User-Klasse hat jetzt eine Methode namens orders(), die besagt, dass ein Benutzer mit mehreren Bestellungen verknüpft werden kann. In der ORM-Welt sagen wir, dass die Orders()-Beziehung hier die Umkehrung der user()-Beziehung ist, die wir im Order-Modell hatten.

Aber Moment mal! Wie funktioniert diese Beziehung? Ich meine, es gibt nichts auf Datenbankebene, das mehrere Verbindungen hat, die von der Benutzertabelle zur Auftragstabelle gehen.

Tatsächlich besteht eine bestehende Verbindung, und es stellt sich heraus, dass sie alleine ausreicht – die in der Orders-Tabelle gespeicherte Fremdschlüsselreferenz! Das heißt, wenn wir so etwas wie $user->orders sagen, trifft Laravel auf die Orders()-Funktion und weiß, indem er sie ansieht, dass es einen Fremdschlüssel in der Orders-Tabelle gibt. Dann führt es eine Art SELECT * FROM-Bestellungen mit WHERE user_id = 23 durch und gibt die Abfrageergebnisse als Sammlung zurück. Natürlich ist der springende Punkt bei einem ORM, SQL zu vergessen, aber wir sollten nicht völlig vergessen, dass die zugrunde liegende Basis das RDBMS ist, das SQL-Abfragen ausführt.

Lassen Sie uns als Nächstes die Bestell- und Rechnungsmodelle durchgehen, bei denen wir eine Eins-zu-Eins-Beziehung haben:

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

Und das Rechnungsmodell:

<?php

namespace AppModels;

use IlluminateDatabaseEloquentFactoriesHasFactory;
use IlluminateDatabaseEloquentModel;

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

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

Beachten Sie, dass es sich auf der Datenbankebene, wie auch fast auf der Eloquent-Ebene, um eine typische Eins-zu-Viele-Beziehung handelt; Wir haben gerade einige Überprüfungen hinzugefügt, um sicherzustellen, dass es eins zu eins bleibt.

Wir kommen jetzt zu einer anderen Art von Beziehung – der Viele-zu-Viele-Beziehung zwischen Bestellungen und Artikeln. Denken Sie daran, dass wir bereits eine Zwischentabelle namens item_order erstellt haben, die die Zuordnung zwischen den Primärschlüsseln speichert. Wenn so viel richtig gemacht wurde, ist es trivial, die Beziehung zu definieren und damit zu arbeiten. Gemäß der Laravel-Dokumentation müssen Ihre Methoden zum Definieren einer Viele-zu-Viele-Beziehung eine Instanz gehörtzuMany() zurückgeben.

Also im Item-Modell:

<?php

namespace AppModels;

use IlluminateDatabaseEloquentFactoriesHasFactory;
use IlluminateDatabaseEloquentModel;

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

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

Überraschenderweise ist die umgekehrte Beziehung fast identisch:

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

Und das ist es! Solange wir die Namenskonventionen korrekt befolgt haben, kann Laravel sowohl die Zuordnungen als auch ihren Standort ableiten.

Da alle drei grundlegenden Arten von Beziehungen behandelt wurden (Eins-zu-Eins, Eins-zu-Viele, Viele-zu-Viele), höre ich auf, die Methoden für andere Modelle aufzuschreiben, da sie in Kürze folgen werden gleichen Linien. Lassen Sie uns stattdessen die Fabriken für diese Modelle erstellen, einige Dummy-Daten erstellen und diese Beziehungen in Aktion sehen!

Wie machen wir das? Nun, lassen Sie uns den Quick-and-Dirty-Pfad nehmen und alles in die Standard-Seeder-Datei werfen. Wenn wir dann die Migrationen ausführen, führen wir auch den Seeder aus. So sieht meine DatabaseSeeder.php-Datei aus:

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

Und jetzt richten wir die Datenbank erneut ein und säen sie:

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

Gut! Jetzt ist der letzte Teil dieses Artikels, in dem wir einfach auf diese Beziehungen zugreifen und alles bestätigen, was wir bisher gelernt haben. Sie werden erfreut sein zu erfahren (hoffentlich), dass dies ein leichter und unterhaltsamer Abschnitt sein wird.

Und jetzt starten wir die lustigste Laravel-Komponente – die interaktive Tinker-Konsole!

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

Zugriff auf Eins-zu-eins-Modellbeziehungen in Laravel Eloquent

Okay, lassen Sie uns zunächst auf die Eins-zu-Eins-Beziehung zugreifen, die wir in unseren Bestell- und Rechnungsmodellen haben:

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

Merkst du etwas? Denken Sie daran, dass diese Beziehung auf Datenbankebene eine Eins-zu-Viele-Beziehung ist, wenn nicht für die zusätzlichen Einschränkungen. Laravel hätte also eine Sammlung von Objekten (oder nur ein Objekt) als Ergebnis zurückgeben können, und das wäre technisch korrekt. ABER . . . Wir haben Laravel gesagt, dass es sich um eine Eins-zu-Eins-Beziehung handelt, das Ergebnis ist also eine einzelne Eloquent-Instanz. Beachten Sie, dass dasselbe passiert, wenn Sie auf die umgekehrte Beziehung zugreifen:

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

Zugriff auf 1:n-Modellbeziehungen in Laravel Eloquent

Wir haben eine Eins-zu-Viele-Beziehung zwischen Benutzern und Bestellungen. Lassen Sie uns jetzt daran „basteln“ und die Ausgabe sehen:

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

Genau wie erwartet führt der Zugriff auf die Bestellungen eines Benutzers zu einer Sammlung von Datensätzen, während die Umkehrung nur ein einziges Benutzerobjekt erzeugt. Mit anderen Worten, eins zu vielen.

Zugriff auf Many-to-Many-Modellbeziehungen in Laravel Eloquent

Lassen Sie uns nun eine Viele-zu-Viele-Beziehung untersuchen. Wir haben eine solche Beziehung zwischen Artikeln und Bestellungen:

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

Diese Ausgabe kann beim Durchlesen etwas schwindelig sein, aber beachten Sie, dass item1 Teil der Artikel von order1 ist und umgekehrt, so haben wir die Dinge eingerichtet. Lassen Sie uns auch einen Blick in die Zwischentabelle werfen, in der die Zuordnungen gespeichert sind:

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

Fazit

Ja, das ist es wirklich! Es war ein wirklich langer Artikel, aber ich hoffe, er war nützlich. Ist das alles, was man über Laravel-Modelle wissen muss?

Traurigerweise Nein. Das Kaninchenloch ist wirklich, wirklich tief, und es gibt viele weitere herausfordernde Konzepte wie polymorphe Beziehungen und Leistungsoptimierung und so weiter, denen Sie begegnen werden, wenn Sie als Laravel-Entwickler wachsen. Im Moment reicht das, was dieser Artikel behandelt, für 70 % der Entwickler in 70 % der Zeit aus, grob gesagt. Es wird wirklich lange dauern, bis Sie das Bedürfnis verspüren, Ihr Wissen zu erweitern.

Abgesehen von diesem Vorbehalt möchte ich, dass Sie diese wichtigste Erkenntnis mitnehmen: Nichts ist dunkle Magie oder in der Programmierung unerreichbar. Es ist nur so, dass wir die Grundlagen und den Aufbau der Dinge nicht verstehen, was uns kämpfen und frustrieren lässt.

So . . . ?

Investiere in dich selbst! Kurse, Bücher, Artikel, andere Programmiergemeinschaften (Python ist meine Empfehlung Nr. 1) – verwenden Sie alle Ressourcen, die Sie finden können, und konsumieren Sie sie regelmäßig, wenn auch langsam. Ziemlich bald wird die Anzahl der Fälle, in denen Sie wahrscheinlich das Ganze verpfuschen, drastisch abnehmen.

Okay, genug gepredigt. Einen schönen Tag noch! 🙂