W czasie rozwoju i utrzymywania aplikacji zasilanej danymi z bazy danych, struktura tej ostatniej ewoluuje podobnie jak
sam kod źródłowy. Przykładowo, rozbudowując aplikację konieczne jest dodanie nowej tabeli, lub też już po wydaniu aplikacji
na serwerze produkcyjnym przydałby się indeks, aby poprawić wydajność zapytania itd. Zmiana struktury bazy danych często
pociąga za sobą zmiany w kodzie źródłowym, dlatego też Yii udostępnia funkcjonalność tak zwanych migracji bazodanowych,
która pozwala na kontrolowanie zmian w bazie danych (migracji).
Poniższe kroki pokazują, jak migracje mogą być wykorzystane przez zespół deweloperski w czasie pracy:
Tomek tworzy nową migrację (np. dodaje nową tabelę, zmienia definicję kolumny, itp.).
Tomek rejestruje (“commit”) nową migrację w systemie kontroli wersji (np. Git, Mercurial).
Mariusz uaktualnia swoje repozytorium z systemu kontroli wersji i otrzymuje nową migrację.
Mariusz dodaje migrację do swojej lokalnej bazy danych, dzięki czemu synchronizuje ją ze zmianami, które wprowadził
Tomek.
A poniższe kroki opisują w skrócie jak stworzyć nowe wydanie z migracją bazy danych na produkcji:
Rafał tworzy tag wydania dla repozytorium projektu, który zawiera nowe migracje bazy danych.
Rafał uaktualnia kod źródłowy na serwerze produkcyjnym do otagowanej wersji.
Rafał dodaje zebrane nowe migracje do produkcyjnej bazy danych.
Yii udostępnia zestaw narzędzi konsolowych, które pozwalają na:
utworzenie nowych migracji;
dodanie migracji;
cofnięcie migracji;
ponowne zaaplikowanie migracji;
wyświetlenie historii migracji i jej statusu.
Powyższe narzędzia są dostępne poprzez komendę yii migrate. W tej sekcji opiszemy szczegółowo w jaki sposób z nich
korzystać. Możesz również zapoznać się ze sposobem użycia narzędzi w konsoli za pomocą komendy pomocy yii help migrate.
Tip: Migracje mogą modyfikować nie tylko schemat bazy danych, ale również same dane, a także mogą służyć do innych zadań
jak tworzenie hierarchi kontroli dostępu dla ról (RBAC) lub czyszczenie pamięci podręcznej.
Note: Modyfikowanie danych w migracji zwykle jest znacznie prostsze, jeśli użyje się do tego klas
Active Record, dzięki logice już tam zaimplementowanej. Należy jednak pamiętać, że logika
aplikacji jest podatna na częste zmiany, a naturalnym stanem kodu migracji jest jego stałość - w przypadku zmian w
warstwie Active Record aplikacji ryzykujemy zepsucie migracji, które z niej korzystają. Z tego powodu kod migracji
powinien być utrzymywany niezależnie od pozostałej logiki aplikacji.
Aby utworzyć nową migrację, uruchom poniższą komendę:
yii migrate/create <nazwa>
Wymagany argument nazwa przekazuje zwięzły opis migracji. Przykładowo, jeśli migracja ma dotyczyć utworzenia nowej
tabeli o nazwie news, możesz użyć jako argumentu create_news_table i uruchomić komendę:
yii migrate/create create_news_table
Note: Argument nazwa zostanie użyty jako część nazwy klasy nowej migracji i z tego powodu powinien składać się tylko
z łacińskich liter, cyfr i/lub znaków podkreślenia.
Powyższa komenda utworzy nowy plik klasy PHP o nazwie podobnej do m150101_185401_create_news_table.php w folderze
@app/migrations. Plik będzie zawierał poniższy kod, gdzie zadeklarowany jest szkielet klasy m150101_185401_create_news_table:
echo"m101129_185401_create_news_table cannot be reverted.\n";
returnfalse;
}
/*
// Use safeUp/safeDown to run migration code within a transaction
public function safeUp()
{
}
public function safeDown()
{
}
*/
}
Każda migracja zdefiniowana jest jako klasa PHP rozszerzająca [[yii\db\Migration]]. Nazwa klasy migracji jest generowana
automatycznie w formacie m<YYMMDD_HHMMSS>_<Nazwa>, gdzie
<YYMMDD_HHMMSS> to data i czas UTC wskazujące na moment utworzenia migracji,
<Nazwa> jest identyczna z wartością argumentu nazwa podanego dla komendy.
Wewnątrz klasy migracji należy napisać kod w metodzie up(), która wprowadzi zmiany w strukturze bazy danych.
Można również napisać kod w metodzie down(), który spowoduje cofnięcie zmian wprowadzonych w up(). Metoda up() jest
uruchamiana w momencie aktualizacji bazy, a down() w momencie przywracania jej do poprzedniego stanu.
Poniższy kod pokazuje, jak można zaimplementować klasę migracji, aby utworzyć tabelę news:
Info: Nie wszystkie migracje są odwracalne. Dla przykładu, jeśli w up() usuwane są wiersze z tabeli, możesz nie być
w stanie przywrócić ich w metodzie down(). Może też zdarzyć się, że celowo nie podasz nic w down() - cofanie zmian
migracji bazy danych nie jest czymś powszechnym - w takim wypadku należy zwrócić false w metodzie down(), aby
wyraźnie wskazać, że migracja nie jest odwracalna.
Podstawowa klasa migracji [[yii\db\Migration]] umożliwia połączenie z bazą danych poprzez właściwość
[[yii\db\Migration::db|db]]. Możesz użyć jej do modyfikowania schematu bazy za pomocą metod opisanych w sekcji
Praca ze schematem bazy danych.
Przy tworzeniu tabeli albo kolumny zamiast używać rzeczywistych typów, powinno się stosować typy abstrakcyjne, dzięki
czemu migracje będą niezależne od pojedynczych silników bazodanowych. Klasa [[yii\db\Schema]] definiuje zestaw stałych,
które reprezentują wspierane typy abstrakcyjne. Stałe te nazwane są według schematu TYPE_<Nazwa>. Dla przykładu,
TYPE_PK odnosi się do typu klucza głównego z autoinkrementacją; TYPE_STRING do typu łańcucha znaków. Kiedy migracja
jest dodawana do konkretnej bazy danych, typy abstrakcyjne są tłumaczone na odpowiadające im typy rzeczywiste. W przypadku
MySQL, TYPE_PK jest zamieniony w int(11) NOT NULL AUTO_INCREMENT PRIMARY KEY, a TYPE_STRING staje się varchar(255).
Możesz łączyć abstrakcyjne typy z dodatkowymi definicjami - w powyższym przykładzie NOT NULL jest dodane do
Schema::TYPE_STRING, aby oznaczyć, że kolumna nie może być ustawiona jako null.
Info: Mapowanie typów abstrakcyjnych na rzeczywiste jest określone we właściwości [[yii\db\QueryBuilder::$typeMap|$typeMap]]
dla każdej klasy QueryBuilder poszczególnych wspieranych silników baz danych.
Począwszy od wersji 2.0.6, możesz skorzystać z nowej klasy budowania schematów, która pozwala na znacznie wygodniejszy
sposób definiowana kolumn. Dzięki temu migracja z przykładu powyżej może być napisana następująco:
Począwszy od wersji 2.0.7 konsola migracji pozwala na wygodne utworzenie nowej migracji.
Jeśli nazwa migracji podana jest w jednej z rozpoznawalnych form, np. create_xxx_table lub drop_xxx_table, wtedy
wygenerowany plik migracji będzie zawierał dodatkowy kod, w tym przypadku odpowiednio kod tworzenia i usuwania tabeli.
Poniżej opisane są wszystkie warianty tej funkcjonalności.
Note: Klucz główny jest dodawany automatycznie i nazwany domyślnie id. Jeśli chcesz użyć innej nazwy, możesz
zdefiniować go bezpośrednio np. --fields="name:primaryKey".
Umiejscowienie słowa foreignKey w definicji kolumny nie ma znaczenia dla generatora, zatem:
author_id:integer:notNull:foreignKey(user)
author_id:integer:foreignKey(user):notNull
author_id:foreignKey(user):integer:notNull
wygenerują ten sam kod.
Opcja foreignKey może być wzbogacona o parametr w nawiasach, który oznacza nazwę tabeli relacji dla generowanego
klucza obcego. Bez tego parametru użyta zostanie nazwa tabeli relacji zgodna z nazwą kolumny.
W przykładzie powyżej author_id:integer:notNull:foreignKey(user) wygeneruje kolumnę o nazwie author_id z kluczem
obcym wskazującym na tabelę user, natomiast category_id:integer:defaultValue(1):foreignKey wygeneruje kolumnę
category_id z kluczem obcym wskazującym na tabelę category.
Począwszy od wersji 2.0.11, dla foreignKey można podać drugi parametr, oddzielony białym znakiem, z nazwą kolumny
relacji dla generowanego klucza obcego. Jeśli drugi parametr nie jest podany, nazwa kolumny jest pobierana ze schematu tabeli.
Jeśli schemat nie istnieje , klucz główny nie jest ustawiony lub jest kluczem kompozytowym, używana jest domyślna nazwa id.
Jeśli nazwa migracji jest w postaci create_junction_table_for_xxx_and_yyy_tables lub create_junction_xxx_and_yyy_tables,
wtedy plik będzie zawierał kod potrzebny do wygenerowania tabeli węzła pomiędzy tabelami xxx i yyy.
Począwszy od wersji 2.0.11, nazwy kolumn kluczy obcych dla tabeli węzła są pobierane ze schematu tabel.
Jeśli tabela nie jest zdefiniowana w schemacie, lub jej klucz główny nie jest ustawiony lub jest kluczem kompozytowym,
używana jest domyślna nazwa id.
Przy wykonywaniu skomplikowanych migracji bazodanowych, bardzo ważnym jest zapewnienie, aby wszystkie ich operacje
zakończyły się sukcesem, a w przypadku niepowodzenia nie zostały wprowadzone tylko częściowo, dzięki czemu baza danych
może zachować spójność. Zalecane jest, aby w tym celu wykonywać operacje migracji wewnątrz transakcji.
Najprostszym sposobem implementacji migracji transakcyjnych jest umieszczenie ich kodu w metodach safeUp() i safeDown().
Metody te różnią się od up() i down() tym, że są wywoływane automatycznie wewnątrz transakcji.
W rezultacie niepowodzenie wykonania dowolnej z operacji skutkuje automatycznym cofnięciem wszystkich poprzenich udanych
operacji.
W poniższym przykładzie oprócz stworzenia tabeli news dodatkowo dodajemy pierwszy wiersz jej danych.
Zwróć uwagę na to, że dodając wiele operacji bazodanowych w safeUp(), zwykle powinieneś odwrócić kolejność ich
wykonywania w safeDown(). W naszym przykładzie najpierw tworzymy tabelę, a potem dodajemy wiersz w safeUp(), natomiast
w safeDown() najpierw kasujemy wiersz, a potem usuwamy tabelę.
Note: Nie wszystkie silniki baz danych wspierają transakcje i nie wszystkie rodzaje komend bazodanowych można umieszczać
w transakcjach. Dla przykładu, zapoznaj się z rozdziałem dokumentacji MySQL
Statements That Cause an Implicit Commit. W przypadku
braku możliwości skorzystania z transakcji, powinieneś użyć up() i down().
Bazowa klasa migracji [[yii\db\Migration]] udostępnia zestaw metod, dzięki którym można połączyć się z i manipulować
bazą danych. Metody te są nazwane podobnie jak metody DAO klasy [[yii\db\Command]].
Przykładowo metoda [[yii\db\Migration::createTable()]] pozwala na stworzenie nowej tabeli, tak jak
[[yii\db\Command::createTable()]].
Zaletą korzystania z metod [[yii\db\Migration]] jest brak konieczności bezpośredniego tworzenia instancji [[yii\db\Command]],
a wywołanie każdej z tych metod dodatkowo wyświetli użyteczne informacje na temat operacji bazodanowych i
czasu ich wykonywania.
Poniżej znajdziesz listę wspomnianych wcześniej metod:
[[yii\db\Migration::addCommentOnColumn()|addCommentOnColumn()]]: dodawanie komentarza do kolumny
[[yii\db\Migration::dropCommentFromColumn()|dropCommentFromColumn()]]: usuwanie komentarza z kolumny
[[yii\db\Migration::addCommentOnTable()|addCommentOnTable()]]: dodawanie komentarza do tabeli
[[yii\db\Migration::dropCommentFromTable()|dropCommentFromTable()]]: usuwanie komentarza z tabeli
Info: [[yii\db\Migration]] nie udostępnia metod dla kwerendy danych. Wynika to z tego, że zwykle nie jest potrzebne
wyświetlanie dodatkowych informacji na temat pobieranych danych z bazy. Dodatkowo możesz zawsze użyć potężnego
Konstruktora kwerend do zbudowania i wywołania skomplikowanych kwerend.
Użycie konstruktora kwerend w migracji może wyglądać następująco:
// uaktualnij kolumnę statusu dla wszystkich użytkowników
Aby uaktualnić bazę danych do najświeższej wersji jej struktury, należy zastosować wszystkie dostępne nowe migracje,
korzystając z poniższej komendy:
yii migrate
Komenda ta wyświetli listę wszystkich migracji, które jeszcze nie zostały zastosowane. Jeśli potwierdzisz, że chcesz je
zastosować, wywoła ona metodę up() lub safeUp() dla każdej z migracji na liście, w kolejności ich znaczników czasu.
Jeśli którakolwiek z migracji nie powiedzie się, komenda zakończy działanie bez stosowania pozostałych migracji.
Tip: Jeśli nie masz dostępu do linii komend na serwerze, wypróbuj rozszerzenie web shell.
Dla każdej udanej migracji komenda doda wiersz do bazy danych w tabeli migration, aby oznaczyć fakt zastosowania migracji.
Pozwoli to na identyfikację, która z migracji została już zastosowana, a która jeszcze nie.
Info: Narzędzie do migracji automatycznie utworzy tabelę migration w bazie danych, wskazaną przez opcję
[[yii\console\controllers\MigrateController::db|db]] komendy. Domyślnie jest to baza danych określona w
komponencie aplikacjidb.
Czasem możesz mieć potrzebę zastosowania tylko jednej bądź kilku nowych migracji, zamiast wszystkich na raz.
Możesz tego dokonać określając liczbę migracji, które chcesz zastosować uruchamiając komendę.
Przykładowo, poniższa komenda spróbuje zastosować następne trzy dostępne migracje:
yii migrate 3
Możesz również dokładnie wskazać konkretną migrację, która powinna być zastosowana na bazie danych, używając komendy
migrate/to na jeden z poniższych sposobów:
yii migrate/to 150101_185401 # używając znacznika czasu z nazwy migracji
yii migrate/to "2015-01-01 18:54:01" # używając łańcucha znaków, który może być sparsowany przez strtotime()
yii migrate/to m150101_185401_create_news_table # używając pełnej nazwy
yii migrate/to 1392853618 # używając UNIXowego znacznika czasu
Jeśli dostępne są niezaaplikowane migracje wcześniejsze niż ta wyraźnie wskazane w komendzie, zostaną one zastosowane
automatycznie przed wskazaną migracją.
Jeśli wskazana migracja została już wcześniej zaaplikowana, wszystkie zaaplikowane aplikacje z późniejszą datą zostaną
cofnięte.
Czasem zamiast aplikowania lub odwracania migracji, możesz chcieć po prostu zaznaczyć, że baza danych zostałą już
uaktualniona do konkretnej migracji. Może się tak zdarzyć, gdy ręcznie modyfikujesz bazę i nie chcesz, aby migracja(e) z
tymi zmianami zostały potem ponownie zaaplikowane. Możesz to osiągnąć w następujący sposób:
yii migrate/mark 150101_185401 # używając znacznika czasu z nazwy migracji
yii migrate/mark "2015-01-01 18:54:01" # używając łańcucha znaków, który może być sparsowany przez strtotime()
yii migrate/mark m150101_185401_create_news_table # używając pełnej nazwy
yii migrate/mark 1392853618 # używając UNIXowego znacznika czasu
Komenda zmodyfikuje tabelę migration poprzez dodanie lub usunięcie wierszy, aby zaznaczyć, że baza danych ma już
zastosowane migracje aż do tej określonej w komendzie. Migracje nie zostaną faktycznie zastosowane lub usunięte.
Komenda migracji ma kilka opcji, które pozwalają na zmianę jej działąnia:
interactive: boolean (domyślnie true), określa czy przeprowadzić migrację w trybie interaktywnym.
Jeśli ustawione jest true, użytkownik będzie poproszony o potwierdzenie przed wykonaniem określonych operacji.
Możesz chcieć zmienić to ustawienie na false, jeśli komenda ma być używana w tle.
migrationPath: string|array (domyślnie @app/migrations), określa folder, gdzie znajdują się wszystkie pliki migracji.
Parametr może być określony jako rzeczywista ścieżka lub alias.
Zwróć uwagę na to, że folder musi istnieć, inaczej okmenda może wywołać błąd. Począwszy od wersji 2.0.12 można tutaj
podać tablicę, aby załadować migracje z wielu źródeł.
migrationTable: string (domyślnie migration), określa nazwę tabeli w bazie danych, gdzie trzymana będzie historia
migracji. Tabela będzie automatycznie stworzona, jeśli nie istnieje.
Możesz również utworzyć ją ręcznie używając struktury version varchar(255) primary key, apply_time integer.
db: string (domyślnie db), określa identyfikator bazodanowego komponentu aplikacji.
Reprezentuje on bazę danych, na której będą zastosowane migracje.
templateFile: string (domyślnie @yii/views/migration.php), określa ścieżkę pliku szablonu, używanego do generowania
szkieletu plików migracji. Parametr może być określony jako rzeczywista ścieżka lub alias.
Plik szablonu jest skryptem PHP, w którym możesz użyć predefiniowanej zmiennej $className, aby pobrać nazwę klasy
migracji.
generatorTemplateFiles: array (domyślnie [ 'create_table' => '@yii/views/createTableMigration.php', 'drop_table' => '@yii/views/dropTableMigration.php', 'add_column' => '@yii/views/addColumnMigration.php', 'drop_column' => '@yii/views/dropColumnMigration.php', 'create_junction' => '@yii/views/createTableMigration.php' ]), określa pliki szablonów do generowania kodu migracji. Po więcej szczegółów przejdź do
“Generowanie migracji”.
fields: tablica definicji kolumn w postaci łańcuchów znaków do wygenerowania kodu migracji. Domyślnie [].
Format każdej definicji to NAZWA_KOLUMNY:TYP_KOLUMNY:DEKORATOR_KOLUMNY. Dla przykładu,
--fields=name:string(12):notNull generuje kolumnę typu “string” o rozmiarze 12, która nie może mieć wartości null.
Poniższy przykład pokazuje jak można użyć tych opcji.
Chcemy zmigrować moduł forum, którego pliki migracji znajdują się w folderze migrations modułu - używamy następującej
komendy:
# stosuje migracje dla modułu forum w trybie nieinteraktywnym
Powyższa konfiguracja powoduje, że z każdym uruchomieniem komendy migracji, tabela backend_migration jest używana do
zapisu historii migracji i nie musisz już określać jej za pomocą opcji linii komend migrationTable.
Począwszy od 2.0.10 możliwe jest używanie przestrzeni nazw w klasach migracji. Możesz zdefiniować listę przestrzeni nazw
za pomocą [[yii\console\controllers\MigrateController::migrationNamespaces|migrationNamespaces]]. Korzystanie z przestrzeni
nazw pozwala na łatwe używanie wielu źródeł migracji. Przykładowo:
Note: Migracje zaaplikowane z różnych przestrzeni nazw będą dodane do pojedynczej historii migracji, przez co np.
niemożliwym jest zastosowanie lub cofnięcie migracji z tylko wybranej przestrzeni nazw.
Wykonując operacje na migracjach z przestrzeni nazw: dodając nowe, odwracając je, itd., należy podać pełną przestrzeń nazw
przed nazwą migracji. Zwróć uwagę na to, że odwrotny ukośnik (\) jest zwykle uważany za znak specjalny linii komend,
zatem musisz odpowiednio zastosować symbol ucieczki, aby uniknąć błędów konsoli i niespodziewanych skutków komendy.
Dla przykładu:
Note: Migracje, których lokalizacja określona jest poprzez
[[yii\console\controllers\MigrateController::migrationPath|migrationPath]] nie mogą zawierać przestrzeni nazw. Migracje
w przestrzeni nazw mogą być zaaplikowane tylko jeśli są wymienione we właściwości
[[yii\console\controllers\MigrateController::migrationNamespaces]].
Począwszy od wersji 2.0.12 właściwość [[yii\console\controllers\MigrateController::migrationPath|migrationPath]] pozwala
również na podanie tablicy wymieniającej wszystkie foldery zawierające migracje bez przestrzeni nazw.
Zmiana ta została wprowadzona dla istniejących projektów, które używają migracji z wielu lokalizacji, głównie z zewnętrznych
źródeł jak rozszerzenia Yii tworzone przez innych deweloperów, które z tego powodu nie mogą łatwo być zmodyfikowane, aby
używać przestrzeni nazw.
Migracje w przestrzeni nazw korzystają z formatu nazw “CamelCase” M<YYMMDDHHMMSS><Nazwa> (przykładowo M190720100234CreateUserTable).
Generując taką migrację pamiętaj, że nazwa tabeli będzie przekonwertowana z formatu “CamelCase” na format “podkreślnikowy”.
Dla przykładu:
Czasem korzystanie z pojedynczej historii migracji dla wszystkich migracji w projekcie jest uciążliwe. Dla przykładu,
możesz zainstalować rozszerzenie ‘blog’, zawierające całkowicie oddzielne funkcjonalności i dostarczające własne migracje,
które nie powinny wpływać na te dedykowane dla funkcjonalności głównego projektu.
Jeśli chcesz, aby część migracji mogła być zastosowana i śledzona całkowicie niezależnie od pozostałych, możesz skonfigurować
kilka komend migracji, które będą używać różnych przestrzeni nazw i tabeli historii migracji:
Domyślnie migracje są stosowane do jednej bazy danych określonej przez
komponent aplikacjidb. Jeśli chcesz, aby były zastosowane do innej bazy, musisz
zdefiniować opcję db w linii komend, jak poniżej,
yii migrate --db=db2
Ta komenda zastosuje migracje do bazy db2.
Czasem konieczne jest, aby zastosować niektóre migracje do jednej bazy, a inne do drugiej. Aby to uzyskać, podczas
implementacji klasy migracji należy bezpośrednio wskazać identyfikator komponentu bazy danych, który migracja ma użyć,
jak poniżej:
Ta migracja będzie zastosowana do bazy db2, nawet jeśli w opcjach komendy określona będzie inna baza.
Zwróć uwagę na to, że historia migracji będzie uaktualniona wciąż w bazie danych określonej przez opcję db linii komend.
Jeśli masz wiele migracji korzystających z tej samej bazy danych, zalecane jest utworzenie bazowej klasy migracji z
powyższym kodem metody init(), a następnie dziedziczenie po niej w każdej kolejnej migracji.
Tip: Oprócz ustawiania właściwości [[yii\db\Migration::db|db]], możesz również operować na różnych bazach poprzez
tworzenie nowych połączeń bazodanowych w klasach migracji, a następnie korzystanie z metod DAO i tych
połączeń.
Inną strategią migracji wielu baz danych jest utrzymywanie migracji dla różnych baz w różnych folderach migracji. Dzięki
temu możesz te bazy migrować w osobnych komendach: