Fremdschlüssel in Laravel 5

In den letzten Tagen musste ich die Datenbank eines auf Laravel 5.4 basierenden Projekts überarbeiten. Dabei war auch der ein oder andere Fremdschlüssel anzulegen, was mich stellenweise fast zur Verzweiflung gebracht hat. Aus diesem Grund möchte ich hier kurz meine Erkenntnisse festhalten in der Hoffnung, dass ich damit anderen helfen kann.

Hier meine Ergebnisse im Überblick:

  • Der Datentyp beider Spalten, also referenzierende Tabelle und referenzierte Tabelle, müssen gleich sein (z.B. INT).
  • Die referenzierende Spalte muss z.B. unsigned sein, wenn die referenzierte Spalte dies auch ist.
  • Werden onDelete/onUpdate-Bedingungen gesetzt, müssen eventuell weitere Vorkehrungen getroffen werden (siehe weiter unten).
  • Eventuell müssen schon vorhandene Einträge in den betroffenen Tabellen angepasst werden.

Was sind überhaupt Foreign Key Constraints bzw. Fremdschlüssel?

Bei einem Fremdschlüssel bzw. Foreign Key Constraint handelt es sich um einen Verweis von einer Datenbanktabelle auf eine andere. In der Regel verweist der Fremdschlüssel auf den Primärschlüssel.

Hier ein kleines Beispiel:

idusernameemailpasswordimage_id
1Fabsfabian@laravelt.depasswort13
2Klausklaus@laravelt.depasswort210
iduser_idnamethumbnailalt
31fabs.jpgfabs_thumbnail.jpgAvatar von Fabs
102klaus.jpgklaus_thumbnail.jpgAvatar von Klaus

Wir haben also eine images-Tabelle und eine users-Tabelle. Beide Tabellen sollen jeweils einen Fremdschlüssel bekommen, der auf den Primärschlüssel der anderen Tabelle verweist.

Konkret bedeutet das:

  • users.image_id ist Fremdschlüssel auf den Primärschlüssel images.id
    • wird das Bild aus images gelöscht, soll das Feld image_id in users auf NULL gesetzt werden.
  • images.user_id ist Fremdschlüssel auf den Primärschlüssel users.id
    • wird ein Benutzer aus users gelöscht, soll das dazugehörige Bild ebenfalls gelöscht werden.

Migrations in Laravel 5

Laravel 5 gibt uns mit den Migrations ein nützliches Werkzeug an die Hand. So müssen wir auf kein zusätzliches Tool zurückgreifen oder reines SQL schreiben.

Ausgehend davon, dass die Tabellen noch nicht existieren, legen wir uns erstmal eine Migration-Datei an. Dies geht einfach, in dem du dein Terminal/deine Konsole/Eingabeaufforderung öffnest und in das Root-Verzeichnis deines Laravel-Projekts wechselst. Nun gibst du folgenden Befehl in die Konsole ein:

Im Ordner */database/migrations/ liegt nun eine neue Datei mit folgendem Inhalt:

Du siehst, dass die Klasse zwei Methoden hat. up() wird ausgeführt, wenn die Migration durchgeführt wird. down() wird bei einem Rollback ausgeführt.

Neue Tabellen anlegen

Tabellen anlegen

Um die nötigen Tabellen anzulegen, ergänzen wir die Datei wie folgt:

In up()legen wir zunächst die beiden Tabellen mit allen Spalten an. Wichtig hier sind die Spalten users.image_id und images.user_id. Diese werden später unsere Fremdschlüssel sein und auf die Primärschlüssel verweisen. Aus diesem Grund sind diese beiden Spalten vom Datentyp her ebenfalls integer und unsigned.

Da ein Benutzer auch ohne Profilbild existieren kann, ist die Spalte users.image_id zusätzlich nullable und bekommt den Defaultwert NULL.

Foreign Key Constraints setzen

Nun ergänzen wir die Fremdschlüssel. Ich rate dazu, dies in einem extra Schritt durchzuführen, also erst, wenn die Tabellen angelegt wurden. So können wir sicher sein, dass die Tabellen auch wirklich schon existieren. Also erweitern wir unsere up()-Funktion:

Die Schreibweise ist dabei eigentlich selbsterklärend. Die aktuelle Tabelle erhält einen Fremdschlüssel auf die Spalte image_id/user_id und referenziert id in der Tabelle users/images.

on delete/on update-Bedingungen bei Fremdschlüsseln

onDelete/onUpdate-Bedingungen sind nützlich, um z.B. den Eintrag einer Tabelle zu löschen/updaten, sobald der referenzierte Eintrag gelöscht wird.

Man unterscheidet folgende Anweisungen:

  • cascade
    • on delete cascade -> der referenzierende Eintrag wird ebenfalls gelöscht
    • on update cascade -> der referenzierende Eintrag wird aktualisiert
  • set null
    • der Fremdschlüssel wird hier auf NULL gesetzt
  • no action bzw. restrict
    • hier passiert nichts

Für unser Beispiel wollen wir zum einen ein on delete cascade erstellen. Dies greift, wenn ein User gelöscht wird. In diesem Fall wird ebenfalls das zugehörige Bild aus images gelöscht.

Zum anderen brauchen wir ein on delete set null, welches greift, wenn z.B. ein User sein Bild löscht. Daraufhin wird in der users-Tabelle das Feld image_id mit NULL belegt.

Wir ergänzen also den Code-Ausschnitt von eben:

Migration ausführen

Zu guter letzt müssen wir die Migration noch ausführen. Dabei übernimmt Laravel für uns die Kommunikation mit der Datenbank und legt die Tabellen mit den zugehörigen Fremdschlüsseln an.

Unsere Migration-Datei sieht im ganzen wie folgt aus:

Um die Migration auszuführen, startet ihr wieder eure Konsole und gebt folgenden Befehl ein:

Ihr bekommt im Erfolgsfall eine Meldung, dass die Migration erfolgreich war.

Vorhandene Tabellen bearbeiten

Nicht immer legt ihr die Fremdschlüssel gleich zu Beginn mit an. Oft genug kommt es vor, dass man im Nachhinein noch Fremdschlüssel vergeben muss. Dies ist unter Umständen etwas komplizierter und lässt sich nicht einfach mit den Migrations von Laravel bewerkstelligen.

Datentyp ändern

Zu den Problemen von Laravel gehört, dass nur eingeschränkt der Datentyp einer Spalte geändert werden kann. Zu den nicht unveränderlichen Datentypen gehören:

char, double, enum, mediumInteger, timestamp, tinyInteger, ipAddress, json, jsonb, macAddress, mediumIncrements, morphs, nullableMorphs, nullableTimestamps, softDeletes, timeTz, timestampTz, timestamps, timestampsTz, unsignedMediumInteger, unsignedTinyInteger, uuid

Ist deine referenzierende Spalte also von einem dieser Datentypen, musst du die Spalte direkt mit SQL oder über ein Datenbankverwaltungswerkzeug (z.B. phpMyAdmin, MySQL-Workbench, Sequel Pro) anpassen. Der SQL-Befehl könnte lauten:

Vorhanden Inhalte müssen berücksichtigt werden

Ist eine Tabelle bereits mit Records belegt, müssen diese eventuell angepasst werden. Soll zum Beispiel ein Fremdschlüssel so gesetzt werden, dass on delete set null ausgeführt wird, müssen die Tabelleninhalte für diese Spalte den Default-Wert NULL haben (also nicht 0 oder ’null‘). Zusätzlich muss diese Spalte nullable sein.

Angenommen, wir haben folgende Tabelle:

idusernameemailpasswordimage_id
1Fabsfabian@laravelt.depasswort10
2Klausklaus@laravelt.depasswort20

Dann müssen wir die Inhalte mit folgendem Befehl anpassen, bevor die Migration ausgeführt werden kann:

Das Ergebnis sieht dann so aus:

idusernameemailpasswordimage_id
1Fabsfabian@laravelt.depasswort1NULL
2Klausklaus@laravelt.depasswort2NULL

So, das war es. Falls du dennoch Probleme mit Migrationen und Fremdschlüsseln in Laravel hast, kannst du gerne ein Kommentar da lassen.

2 Kommentare


  1. Hallo Fabian,

    danke für deinen Beitrag zur deutschen Laravel-Gemeinschaft.

    Wenn ich nicht irre hast du einen Copy&Paste-Fehler, der sich hartnäckig durch alle deine Listings zieht. Der Fremdschlüssel in der Tabelle ‚users‘ sollte über die image_id auf das Feld ‚id‘ in die Tabelle ‚images‘ verweisen, anstatt auf ‚users‘.

    Antworten

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.