Tworząc aplikacje każdy z nas wykorzystuje jakiś system kontroli wersji, który pozwala w łatwy sposób kontrolować kod aplikacji. Możemy w każdej chwili cofnąć się w czasie do wersji wcześniejszej, czy też w łatwy sposób zaktualizować starsze wersje aplikacji. Co zaś z bazą danych i zarządzaniem jej strukturą w ramach wydawania kolejnych wersji aplikacji ?
Z pomocą przychodzi nam rozwiązanie Doctrine Migrations jeśli korzystacie z Symfony i Doctrin-a. Inną opcją jest skorzystanie z rozwiązania o nazwie Phinx pozwalającego także na zarządzanie migracjami bazy danych.
Zanim przejdziemy do konkretnych rozwiązań chciałbym przybliżyć nieco ideę, która za owymi rozwiązaniami stoi. Otóż, gdy rozwijamy aplikację przez dłuższy okres czasu zaczynamy standaryzować proces wydawniczy poprzez zastosowanie wersji. Jeśli przyjrzymy się różnym aplikacją to zauważymy, że praktycznie każda posiada jakiś numer wydanej wersji.
I tak też będzie z naszymi aplikacjami jeśli będziemy je rozwijać przez dłuższy okres czasu. W przypadku aplikacji webowych, którymi zajmuję się na codzień dodajemy jakieś funkcjonalności. W większości wypadków wiąże się to z koniecznością modyfikacji struktury bazy danych. Wymaga to od nas jakiegoś mechanizmu pozwalającego na łatwą aktualizację struktury bazy w ramach wydawania kolejnej wersji aplikacji.
Załóżmy że mamy listę klientów i otrzymaliśmy nowe zadanie polegające na dodaniu dodatkowego pola na adres e-mail. Oczywiście wymaga to konieczności modyfikacji modelu, formularzy, dodaniu walidatorów itd. Tę część bez problemu załatwi nam GIT w połączeniu z techniką GIT Flow jednak konieczna jest modyfikacja struktury bazy danych.
Oczywiście możemy założyć że wejdziemy do bazy i dokonamy stosownej modyfikacji. Jednak co będzie jeśli tych baz będzie 5, 10, 50… i co jeśli będziemy musieli przeskoczyć z wersji 1.1.0 na 1.24.2 ?
Oczywiście możemy tworzyć pliki SQL, które będą wykonywały takowe operacje jednak na dłuższą metę jest to rozwiązanie mało wygodne. Dlatego chciałem zainteresować was rozwiązaniami Doctrime Migrations.
Osoby mające do czynienia z frameworkiem Symfony zapewne znają lub słyszały o tym rozwiązaniu. Pozwala ono w łatwy sposób generować pliki migracji na podstawie encji co praktycznie załatwia za nas większość pracy.
Podobnie jak w przypadku innych pakietów instalujemy go wykorzystując composer-a wydając polecenie:
php composer.phar require doctrine/doctrine-migrations-bundle "^1.0"
Spowoduje ono dodanie wpisu w pliku composer.json
{
"require": {
"doctrine/doctrine-migrations-bundle": "^1.0"
}
}
Jeśli po drodze nie spotkała nas żadna niemiła niespodzianka to teraz włączamy pakiet w AppKernel.php
poprzez dodanie wpisu:
public function registerBundles()
{
$bundles = array(
//...
new Doctrine\Bundle\MigrationsBundle\DoctrineMigrationsBundle(),
);
}
Ostatni krok to dodanie konfiguracji do pliku config.yml
, gdzie wskazujemy katalog w którym będą przechowywane migracje, nazwę tabeli w bazie danych oraz parę innych parametrów. Na końcu pliku dodajemy wpis:
doctrine_migrations:
dir_name: "%kernel.root_dir%/DoctrineMigrations"
namespace: Application\Migrations
table_name: migration_versions
name: Application Migrations
Poszczególne parametry oznaczają:
%kernel.root_dir%
i w nim ma znaleźć się katalog o nazwie DoctrineMigrations
,Jeśli wszystko wykonaliśmy poprawnie to w linii poleceń po wykonaniu polecenia:
php bin/console doctrine:migrations
Otrzymamy komunikat o braku samego polecenia, jednak zostanie nam wyświetlona lista poleceń które mogliśmy mieć na myśli:
I na tej liście powinniśmy znaleźć polecenia, które zaznaczyłem na zielono. One właśnie są dostarczone wraz z pakietem Doctrine Migrations.
Wszelkie operacje do jakich otrzymujemy dostęp są wykonywane z linii poleceń. Nie ma co się przerażać bowiem zostaje nam udostępnionych zaledwie kilka poleceń.
doctrine:migrations:generate
doctrine:migrations:migrate
doctrine:migrations:version
doctrine:migrations:execute
doctrine:migrations:status
doctrine:migrations:latest
doctrine:migrations:diff
I tak mamy polecenie do sprawdzenia aktualnej wersji naszego schematu bazy danych:
php bin/console doctrine:migrations:status
Efektem działania polecenia będzie wyświetlenie pełnego zestawu informacji.
Jak widać powyżej mamy informacje o:
Powyższa lista to tylko najważniejsze informacje wyświetlane przez polecenie doctrine:migrations:status
. Wszystkie te informacje są ustalane na podstawie tabeli w bazie danych oraz listy plików zawartych w katalogu z migracjami.
Jako że po instalacji nie posiadamy żadnych migracji, a migracje chcemy zacząć używać w już działającej aplikacji. Wyjścia mamy dwa, pierwsze to zapominamy o przeszłości i migrujemy tylko kolejne wydania aplikacji. Da się z tym żyć jednak ma jedną wadę, znacznie utrudnia automatyzację procesu instalacji. Bo czy nie jest prościej w konsoli wydać jedno polecenie:
php bin/console doctrine:migrations:migrate
I po chwili mieć aktualną strukturę bazy danych, niż importować jakiś plik SQL do bazy danych na serwerze i dopiero uruchamiać dalsze migracje?
Rozwiązanie drugie, generujemy plik migracji i wrzucamy do niego bieżącą strukturę bazy danych. Wygenerowanie nowego pliku migracji wykonujemy poleceniem:
php bin/console doctrine:migrations:generate
Po chwili pojawi się nam nowy plik w zdefiniowanym katalogu z migracjami. Jego nazwa jest specyficzna:
Version20161116000000
Budowa nazwy pliku jest trywialna i składa się ze stałego tekstu Version
oraz daty i czasu wygenerowanego pliku.
Nazwa ta ma pokrycie w nazwie klasy, która znajduje się wewnątrz pliku.
namespace Application\Migrations;
use Doctrine\DBAL\Migrations\AbstractMigration;
use Doctrine\DBAL\Schema\Schema;
/**
* Auto-generated Migration: Please modify to your needs!
*/
class Version20161116000000 extends AbstractMigration
{
}
Standardowo plik posiada dwie metody, metoda up
jest odpowiedzialna za wykonanie aktualizacji schematu. Zaś metoda down
odpowiada za cofnięcie zmian w schemacie wykonanych przez metodę up
. Zaimplementujmy więc obie metody, aby zobaczyć jak działa ten mechanizm.
public function up(Schema $schema)
{
$this->addSql(
'CREATE TABLE `companies` (
`id` int(10) UNSIGNED NOT NULL,
`name` varchar(255) COLLATE utf8_unicode_ci NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;'
);
}
Mamy już pierwszą migrację gotową teraz czas dodać operację cofającą wprowadzane zmiany.
public function down(Schema $schema)
{
$this->addSql('DROP TABLE `companies`');
}
Po tym wszystkim możemy zobaczyć jak zmienił się status migracji poleceniem:
php bin/console doctrine:migrations:status
W rezultacie zobaczymy, że mamy jedną migrację do uruchomienia.
Uruchamiamy migrację poleceniem:
php bin/console doctrine:migrations:migrate
Wiemy już jak tworzyć migracje ręcznie, jednak możliwe jest tworzenie migracji automatycznie. Plik migracji wtedy jest generowany na podstawie różnicy pomiędzy encjami, a aktualną strukturą bazy danych. Migrację różnicową uruchamiamy poleceniem:
php bin/console doctrine:migrations:diff
Podobnie jak w poprzednim przypadku zostanie wygenerowany plik z przygotowanymi wpisami migracji oraz jej cofania. My zaś jedyne co musimy uruchomić migrację.
php bin/console doctrine:migrations:migrate
Poznaliśmy już podstawowe polecenie uruchamiania migracji:
php bin/console doctrine:migrations:migrate
Polecenie to uruchamia wszystkie migracje, które jeszcze nie zostały wprowadzone. Jednak możemy zdecydować o uruchomieniu jedynie kolejnej migracji poleceniem:
php bin/console doctrine:migrations:migrate next
Jeśli chcielibyśmy natomiast cofnąć migrację to operację tę wykonujemy analogicznym poleceniem:
php bin/console doctrine:migrations:migrate prev
Co spowoduje cofnięcie jednej migracji. I tak możemy się cofać aż do pierwszej migracji, jest jednak szybszy sposób na przejście do określonej migracji. Wystarczy wywołać polecenie:
php bin/console doctrine:migrations:migrate YYYYMMDDHHMMSS
Doctrine Migrations to wspaniałe narzędzie wspomagające zarządzanie schematem bazy danych. Dzięki integracji z Symfony i Doctrine mamy możliwość generowania w zautomatyzowany sposób pliki migracji na podstawie tworzonych encji. Jeśli jeszcze nie mieliście okazji korzystać z tego narzędzia to gorąco zachęcam.