Poznajemy lepiej migracje dokonujące zmian w już istniejących tabelach. Lekcja teoretyczna, ale musimy przez to przejść. Do dzieła.

Ok, przypomnijmy sobie lekcję poprzednią, dokładniej naszą migrację:

return new class extends Migration
{
    /**
     * Run the migrations.
     */
    public function up(): void
    {
        Schema::table('notes', function (Blueprint $table) {
            $table->boolean('hidden')->default(false);
        });
    }

    /**
     * Reverse the migrations.
     */
    public function down(): void
    {
        Schema::table('notes', function (Blueprint $table) {
            $table->dropColumn('hidden');
        });
    }
};

Dowiedzieliśmy się jak dodać kolumnę i jak ją ustawić w metodzie down, aby możliwy był rollback.

A gdybyśmy tak chcieli dodać softDeletes to już istniejącej tabeli? Jak najbardziej możliwe:

Schema::table('flights', function (Blueprint $table) {
    $table->softDeletes();
});
 
Schema::table('flights', function (Blueprint $table) {
    $table->dropSoftDeletes();
});

Oczywiście dropColumn(’deleted_at’) też chyba by zadziałało.

Ok, a co jeśli chcemy dodać klucz obcy w migracji, np. :

$table->unsignedBigInteger('person_id')->nullable(true);
$table->foreign('person_id')->references('id')->on('people')->onDelete('cascade');

No tu robimy dwie rzeczy, po pierwsze tworzymy kolumnę (wiemy jak ją dropnąć w down), po drugie tworzymy klucz obcy. Wejdźmy w notes, w widok relacyjny i zobaczmy, co tam mamy:

Constaint properties:          Column:    DB:        Table:     Column:
notes_person_id_foreign        person_id  layoutapp  people     id

No jak widać kolumna person_id to coś więcej niż tylko integer z liczbą, która przypadkiem odpowiada jakiejś liczbie id w tabeli people. Jeżeli umiemy tworzyć tabele z palca, to powinniśmy to wiedzieć, jeżeli polegamy na frameworkach i ORMach to pewnie nas to dziwi, że person_id to coś wiecej niż zwykły int, jakim byłoby np. age.

Cóż, w Symfony jest tak, że możemy utworzyć sobie Entity fajnym kreatorem make, potem utworzyć migrację (kolejność odwrotna niż w Laravelu) i zobaczymy tam czysty SQL, jaki zostanie użyty.

A co za tym idzie – jeżeli jesteśmy słabi w tworzeniu tabel, ale znamy Symfony to zawsze możemy utworzyć tam entities, zrobić migrację i dostać gotowy DDL (data definition language) do wklejenia i już jest gotowa tabelka.

Ale wracając do naszego foreign key, ten constraint też trzeba dropnąć (przykład tego, co w down, a nazwę pobieramy z widoku relacyjnego):

$table->dropForeign('posts_user_id_foreign');

Oczywiście takich przygód nie mamy w ogóle, jeżeli wszystko robimy w jednej migracji tworzącej tabelę i wszystkie kolumny, wtedy tylko dropIfExists w down. Realistycznie jednak, pracując w większych projektach będziemy tworzyć migracje zmieniające tabele, dodające kolumny, musimy zatem umieć napisać do nich metodę down pozwalającą na rollback.

Nie ma nic złego, jeżeli jesteśmy dobrze zorganizowani i potrafimy tak rozplanować pracę, że jedna migracja załatwia nam jedną tabelę. Ale prędzej czy później i tak przyjdzie pora, że coś trzeba będzie zmodyfikować.

To oczywiście nie koniec nauki. Rzućmy okiem na ten przykład:

class RenameStnkColumn extends Migration
{

    public function up()
    {
        Schema::table('stnk', function(Blueprint $table) {
            $table->renameColumn('id', 'id_stnk');
        });
    }


    public function down()
    {
        Schema::table('stnk', function(Blueprint $table) {
            $table->renameColumn('id_stnk', 'id');
        });
    }

}

Stare toto, jeszcze migracje były klasami nazwanymi, nie anonimowymi, z tych czasów (przykład z internetu pierwszy z brzegu). Tutaj robimy zmianę nazwy kolumny, w down ten proces musimy odwrócić, żeby się wszystko zgadzało.

Inne rzeczy, do których jeszcze wrócimy, ale oswajajmy się z ich istnieniem, zawsze warto wiedzieć także ile jeszcze się nie zna:

$table->renameIndex('from', 'to')

Dropnięcie indeksu:

Schema::table('geo', function (Blueprint $table) {
    $table->dropIndex(['state']); // Drops index 'geo_state_index'
});

Nowy, krótszy zapis foreignKey (nie trzeba tworzyć inta osobno):

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

Dla porównania – po staremu:

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

Zastanówmy się, czy to nam coś mówi:

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

Jak nie to odwiedźmy raz jeszcze notes i widok relacyjny.

Tabele piwotalne kiedyś robiliśmy i jeszcze porobimy, ale zastanówmy się – a co jeżeli dodać timestampy do takich tabeli?

Pewien przykład:

public function threads()
    {
        return $this->belongsToMany('App\Thread')->withTimestamps();
    }

Czyli tutaj sama tabela piwotalna używa timestamps. A gdyby tak tylko jednego używała?

public function threads()
{
    return $this->belongsToMany('App\Thread')->withPivot('created_at');
}

Swoją drogą widzimy jak przekazywaliśmy nazwy klas (z namespace) w stringach, zanim dostaliśmy metodę ::class. To wszystko będziemy musieli przerobić już wkrótce.