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.