Marcin Lewandowski
Marcin Lewandowski
Programista PHP ( Symfony ), blogger, trener oraz miłośnik kawy. Na co dzień pracuję z Symfony, RabbitMQ, ElasticSearch, Node.js, Redis, Docker, MySQL.

Doctrine Migrations czyli łatwe zarządzanie schematem bazy danych

Doctrine Migrations czyli łatwe zarządzanie schematem bazy danych

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.

Koncepcja

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.

Doctrine 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.

Instalacja

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ą:

  • dir_name – ścieżka do katalogu w którym będą przechowywane migracje, w tym przypadku została wskazana ścieżka do katalogu głównego %kernel.root_dir% i w nim ma znaleźć się katalog o nazwie DoctrineMigrations,
  • namespace – przestrzeń nazw,
  • table_name – nazwa tabeli w bazie danych, gdzie będą przechowywane informacje o zastosowanych migracjach,
  • name – dowolna nazwa, która jest wyświetlana przy wykonywaniu migracji

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.

Jak używać

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:

  • Current Version – numer bieżącej wersji,
  • Previous Version – numer poprzedniej wersji,
  • Next Version – numer kolejnej wersji jeśli występuje,
  • New Migrations – ile migracji brakuje nam do wersji aktualnej

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.

Tworzenie migracji

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

Tworzenie migracji na podstawie encji

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

Uruchamianie migracji

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

Podsumowanie

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.