Poznajemy foreign key constraint, tym razem w Laravelu. Po tym zostają nam już tylko check constraints, których w Laravelu nie będziemy nawet używać, mamy walidację. Do dzieła.

Ok, przypomnijmy sobie ten kod:

CREATE TABLE `addresses` ( 
    id BIGINT  UNSIGNED NOT NULL AUTO_INCREMENT,
    person_id BIGINT UNSIGNED NOT NULL,
    city VARCHAR(255) NOT NULL,
    street VARCHAR(255) NOT NULL,
    PRIMARY KEY(id),
    FOREIGN KEY (person_id) REFERENCES people(id) ON DELETE CASCADE ON UPDATE CASCADE
   );

Mam nadzieję, że pamiętamy co on robi, jak nie proszę wrócić do poprzednich lekcji. Teraz nullable klucz obcy, on update set null, nazwane constraint:

CREATE TABLE `addresses` ( 
    id BIGINT  UNSIGNED NOT NULL AUTO_INCREMENT,
    person_id BIGINT UNSIGNED,
    city VARCHAR(255) NOT NULL,
    street VARCHAR(255) NOT NULL,
    PRIMARY KEY(id),
    CONSTRAINT FK_PersonAddr
    FOREIGN KEY (person_id) REFERENCES people(id) ON DELETE CASCADE ON UPDATE SET NULL
   );

Ok, przypomnijmy sobie tę migrację:

public function up(): void
    {
        Schema::create('notes', function (Blueprint $table) {
            $table->id();
            $table->timestamps();
            $table->softDeletes();
            $table->unsignedBigInteger('person_id')->nullable(true);
            //(...)
            $table->foreign('person_id')->references('id')->on('people')->onDelete('cascade');
        });
    }

Tworzymy nullable unsigned big int, tworzymy (z automatyczną nazwą constrainta) klucz obcy, ustalamy jak ma się odnosić do people, ustawiamy onDelete cascade.

Nie mamy onUpdate, więc będzie restrict (nie można robić update numeru id osoby, na którą pokazuje notatka).

Oczywiście możemy to poprawić:

public function up(): void
    {
        Schema::create('notes', function (Blueprint $table) {
            //(...)
            $table->unsignedBigInteger('person_id')->nullable(true);
            $table->foreign('person_id')->references('id')->on('people')->onDelete('cascade')->onUpdate('set null');
        });
    }

Możemy to zrobić tylko dlatego, że person_id jest nullable. W innym wypadku ustawilibyśmy onUpdate na cascade (restrict nie wydaje się dobrą opcją).

Ok, zobaczmy na tę migrację:

public function up(): void
    {
        Schema::create('reviews', function (Blueprint $table) {
            $table->id();
            $table->timestamps();
            $table->string('author');
            $table->text('content');
            $table->tinyInteger('rating')->unsigned();
            $table->foreignId('movie_id')->constrained(
                table: 'movies', indexName: 'reviews_movie_id'
            )->onDelete('cascade')->onUpdate('cascade');
            
        });
    }

Tutaj nie tworzymy kolumny movie_id. To znaczy nie tworzymy unsignedBigInteger ręcznie i potem przypisujemy klucz obcy, tylko wszystko za jednym zamachem robimy.

Mało tego – nazywamy sami sobie constraint (możemy przejść do bazy danych, tabeli, zakładka struktura, zakładka widok relacyjny).

Jest tylko jeden problem – skoro nie tworzymy ręcznie movie_id, to nie może on być nullable (a co za tym idzie onUpdate nie może być set null).

Na to jest rada: ->nullable przed ->constrained:

$table->foreignId('user_id')
      ->nullable()
      ->constrained();

Jeszcze raz – stare podejście:

Schema::table('posts', function (Blueprint $table) {
    $table->unsignedBigInteger('user_id');
 
    $table->foreign('user_id')->references('id')->on('users');
});

Nowe podejście z założeniem, że konwencje nazewnicze są zachowane i Laravel się domyśli tabeli i nazwy (a nazwa constrainta nas nie interesuje):

Schema::table('posts', function (Blueprint $table) {
    $table->foreignId('user_id')->constrained();
});

Nowe podejście, ale chcemy ustawić nazwę tabeli i nazwę constrainta (ten z widoku relacyjnego najbardziej po lewej):

Schema::table('posts', function (Blueprint $table) {
    $table->foreignId('user_id')->constrained(
        table: 'users', indexName: 'posts_user_id'
    );
});

To teraz do kupy, nullable, onDelete, onUpdate:

$table->foreignId('user_id')
      ->constrained(
        table: 'users', indexName: 'posts_user_id'
      )->onUpdate('cascade')
      ->onDelete('cascade');

I to by było na tyle, mam nadzieję, że już dobrze temat ogarniamy.