Datenbankmigration

Hinweis: Das Feature zur Datenbankmigration ist seit Verison 1.1.6 verfügbar.

Genause wie der Quelltext einer Anwendung verändert sich auch die Struktur der zugehörigen Datenbank während der Entwicklung. So muss z.B. irgendwann eine Tabelle hinzugefügt werden. Oder nachdem die Anwendung produktiv geschaltet wurde, stellt man fest, dass eine Spalte noch dringend einen Index benötigt. Es ist daher genauso wichtig wie beim Quelltext, dass auch die strukturellen Änderungen in der Datenbank festgehalten werden (man spricht hier auch von Migration). Wenn Programmcode und Datenbank nicht mehr zueinander passen, ist ein Fehlverhalten praktisch vorprogrammiert. Yii enthält daher ein Tool zur Datenbankmigration, dass eine Historie von Migrationen verwalten, neue Migrationen durchführen oder existierende rückgangig machen kann.

Der folgende Ablauf soll verdeutlichen, wie Datenbanmigration bei der Entwicklung eingesetzt wird:

  1. Tim erstellt eine Migration (er erstellt z.B. eine neue Tabelle)
  2. Tim überträgt seine Änderung in ein Versionskontrollsystem (z.B. SVN, GIT)
  3. Doug updated seine Arbeitskopie und erhält die neue Migration
  4. Doug wendet die Migration auf seiner lokalen Datenbank an

Migrationen werden in Yii über den Kommandozeilenbefehl yiic migrate verwaltet. Mit diesem Befehl können neue Migrationen erstellt, angewendet, zurückgenommen oder wiederholt werden. Man kann auch die Historie aller Migrationen oder neue Migrationen anzeigen lassen.

Hinweis: Es wird empfohlen, den migrate-Befehl mit der yiic-Version im Applikationspfad (z.B. cd pfad/zu/protected) statt derjenigen im Framework-Verzeichnis auszuführen. Stellen Sie sicher, dass das protected/migrations-Verzeichnis existiert und beschreibbar ist. Prüfen Sie außerdem, ob Sie eine Datenbankverbindung in protected/config/console.php angegeben haben.

1. Migrationen erstellen

Um eine neu Migration (z.B. zum Anlegen einer News-Tabelle) zu erzeugen, verwendet man folgenden Befehl:

yiic migrate create <name>

Der Parameter name dient dazu, die Migration kurz und knapp zu beschreiben (z.B. erstelle_news_tabellle). Wie wir weiter unten zeigen, wird dieser Name Bestandteil eines PHP-Klassennamens werden. Er sollte daher nur Buchstaben, Zahlen und/oder Unterstriche enthalten.

yiic migrate create erstelle_news_tabelle

Damit wird im Verzeichnis protected/migrations eine Datei m101129-185401_erstelle_news_tabelle.php mit diesem Inhalt erzeugt:

class m101129_185401_erstelle_news_tabelle extends CDbMigration
{
    public function up()
    {
    }
 
    public function down()
    {
        echo "m101129_185401_erstelle_news_tabelle unterstützt migration down nicht.\n";
        return false;
    }
 
    /*
    // Falls Transaktionen benötigt werden, kann stattdessen safeUp/safeDown implementiert werden
    public function safeUp()
    {
    }
 
    public function safeDown()
    {
    }
    */
}

Beachten Sie, dass der Klassenname dem Dateinamen entspricht und dem Muster m<zeitstempel>_<name> folgt, wobei <zeitstempel> für den UTC-Zeitstempel (im Format JJMMTT_HHMMSS) des Erstellzeitpunkts und <name> für den obigen Migrationsnamen steht.

Die up()-Methode sollte den Code für die eigentliche Migration enthalten, down() den Code, um die Änderung rückgängig zu machen.

Manchmal kommt es vor, dass eine down()-Methode nicht möglich ist, zum Beispiel wenn in up() einige Tabellenzeilen gelöscht wurden. In diesem Fall wird die Migration irreversibel genannt. Man kann sie also nicht rückgängig machen und die Datenbank in einen früheren Zustand zurückfahren. Im generierten Code oben liefert die down()-Methode false zurück um dies anzuzeigen.

Info: Seit Version 1.1.7 werden alle folgenden Migrationen abgebrochen, falls up() oder down() den Wert false zurückgeben. In Version 1.1.6 musste man dazu noch eine Exception werfen.

Das folgende Beispiel zeigt die Migration zum Erstellen einer News-Tabelle.

class m101129_185401_erstelle_news_tabelle extends CDbMigration
{
    public function up()
    {
        $this->createTable('tbl_news', array(
            'id' => 'pk',
            'title' => 'string NOT NULL',
            'content' => 'text',
        ));
    }
 
    public function down()
    {
        $this->dropTable('tbl_news');
    }
}

Die Basisklasse CDbMigration stellt einige Methoden zum Manipulieren von Daten und DB-Schemas bereit. Mit CDbMigration::createTable kann man zum Beispiel eine Tabelle erzeugen, während CDbMigration::insert Zeilen in eine Tabelle einfügt. Alle Methoden verwenden die Datenbankverbindung, die von CDbMigration::getDbConnection() zurückgeliefert wird. Standardmäßig ist das Yii::app()->db.

Info: Sie werden feststellen, dass die DB-Methoden von CDbMigration denen von CDbCommand sehr ähneln. Sie sind tatsächlich auch fast identisch, außer, dass bei den CDbMigration-Methoden zusätzlich die Zeit gemessen, sowie einige Informationen über die Parameter ausgegeben werden.

2. Transaktionen in Migrationen

Info: Dieses Feature steht seit Version 1.1.7 zur Verfügung.

Werden komplexe Migrationen auf einer Datenbank ausgeführt, möchte man für gewöhnlich sicherstellen, dass jede einzelne Migration erfolgreich war. Falls nicht, sollen alle Migrationen abgebrochen werden, um die Datenbank in einem konsistenten Zustand zu hinterlassen. Zu diesem Zweck kann man DB-Transaktionen einsetzen.

Man könnte hierzu explizit eine Transaktion starten und sämtlichen DB-bezogenen Code innerhalb dieser Transaktion ausführen:

class m101129_185401_erstelle_news_tabelle extends CDbMigration
{
    public function up()
    {
        $transaction=$this->getDbConnection()->beginTransaction();
        try
        {
            $this->createTable('tbl_news', array(
                'id' => 'pk',
                'title' => 'string NOT NULL',
                'content' => 'text',
            ));
            $transaction->commit();
        }
        catch(Exception $e)
        {
            echo "Exception: ".$e->getMessage()."\n";
            $transaction->rollback();
            return false;
        }
    }
 
    // ...ähnlicher Code für down()
}

Stattdessen kann man aber auch einfach die Funktionen safeUp() statt up() und safeDown() statt down() implementieren:

class m101129_185401_erstelle_news_tabelle extends CDbMigration
{
    public function safeUp()
    {
        $this->createTable('tbl_news', array(
            'id' => 'pk',
            'title' => 'string NOT NULL',
            'content' => 'text',
        ));
    }
 
    public function safeDown()
    {
        $this->dropTable('tbl_news');
    }
}

Wenn Yii die Migrationen durchführt, startet es automatisch eine DB-Transaktion und führt danach safeUp() oder safeDown() aus. Falls diese Methoden einen Datenbankfehler verursachen, wird die Transaktion zurückgefahren. Damit wird gewährleistet, dass die Datenbank in einem definierten Status verbleibt.

Hinweis: Nicht alle DBMS unterstützen Transaktionen. Einige DB-Befehle können auch gar nicht in Transaktionen gesetzt werden. In diesen Fällen müssen sie stattdessen up() und down() implementieren. Beachten Sie für MySQL außerdem, dass einige Ausdrücke ein implizites Commit verursachen.

3. Migrationen anwenden

Um alle verfügbaren neuen Migrationen anzuwenden (um die lokale Datenbank also auf den aktuellsten Stand zu bringen) ruft man diesen Befehl auf:

yiic migrate

Der Befehl listet alle neuen Migrationen auf. Bestätigt man die Durchführung der Migrationen, werden sämtliche up()-Methoden aller neuen Migrationsklassen ausgeführt. Die Reihenfolge entspricht dabei dem Zeitstempel im Klassennamen.

Nachdem eine Migration angewendet wurde, speichert das Migrationstool dazu einen Datensatz in einer Tabelle namens tbl_migration. Das ermöglicht dem Tool, festzustellen, welche Migrationen schon angewendet wurden und welche nicht. Falls die Tabelle tbl_migration nicht existiert, wird das Tool sie automatisch anlegen und zwar in der Datenbank, die in der db-Komponente konfiguriert wurde.

Manchmal kommt es vor, dass man nur eine oder wenige Migrationen durchführen möchte. Das geht mit diesem Befehl:

yiic migrate up 3

Damit werden 3 neuen Migrationen angewendet. Die Zahl bestimmt also, wieviele Migrationen man anwenden möchte.

Man kann die Datenbank mit folgendem Befehl auch auf eine bestimmte Version migrieren:

yiic migrate to 101129_185401

Man gibt also den Zeitstempel-Teil des Migrationsnamen an, um die Version zu spezifizieren, zu der man migrieren möchte. Sämtliche Migrationen zwischen der aktuellen lokalen DB-Version und dem angegebenen Zeitstempel werden somit durchgeführt. Wurde die angegebene Migration bereits durchgeführt, so werden alle späteren Migrationen rückgängig gemacht (siehe unten).

4. Migrationen rückgängig machen

Um die letzte(n) angewendete(n) Migration(en) rückgängig zu machen, kann man folgenden Befehl verwenden:

yiic migrate down [schritt]

wobei der optionale schritt-Paramter angibt, wie viele Migrationen man rückgängig machen möchte. Standardmäßig steht er auf 1, wodurch die letzte Migration zurückgefahren wird.

Wie oben erwähnt, können nicht alle Migrationen rückgängig gemacht werden. Versucht man eine irreversible Migration rückgängig zu machen, wird eine Exception geworfen und der gesamte Prozess beendet.

5. Migrationen wiederholen

Migrationen zu wiederholen bedeutet, sie erst rückgängig zu machen und dann erneut anzuwenden. Das geschieht mit diesem Befehl:

yiic migrate redo [schritt]

wobei der optionale schritt-Parameter wieder angibt, wie viele Migrationen wiederholt werden sollen. Standardmäßig wird auch hier die letzte Migration wiederholt.

6. Anzeigen von Migrationsinformationen

Das Tool kann nicht nur Migrationen anwenden und rückgängig machen, sondern auch die Historie der durchgeführten Migrationen bzw. die Liste der neuen Migrationen anzeigen.

yiic migrate history [limit]
yiic migrate new [limit]

Der optionale Parameter limit gibt an, wieviele Migrationen maximal angezeigt werden sollen. Wird er weggelassen, werden alle Migrationen angezeigt.

Der erste Befehl zeigt alle angewendenten Migrationen, der zweite die Liste der neuen, die noch nicht durchgeführt wurden.

7. Ändern der Migrations-Historie

Es kommt vor, dass man die Migrationshistorie auf eine bestimmte Version setzen möchte, ohne die Migration tatsächlich durchzführen oder zurückzufahren. Gerade wenn man eine neue Migration erstellt ist dies häufig der Fall. Dazu dient der folgende Befehl:

yiic migrate mark 101129_185401

Der Befehl ist ähnlich zu yiic migrate to, außer, dass er nur die Migrationshistorie auf die angegebene Version setzt, ohne Migrationen durchzuführen oder zurückzufahren.

8. Anpassen des Migrationsbefehls

Der Migrationsbefehl kann auf verschiedene Arten angepasst werden.

Über Kommandozeilen-Optionen

Der Migrationsbefehl kennt vier Optionen, die an der Kommandozeile angegeben werden können:

  • interactive: boolean, gibt an, ob die Migrationen im interaktiven Modus ausgeführt werden soll, was standardmäßig der Fall ist. Der Anwender wird so vor der Durchführung einer Migration gefragt. Setzt man die Option auf false wird die Migration im Hintergrund durchgeführt.

  • migrationPath: string, legt das Verzeichnis mit allen Migrationsdateien fest. Es muss ein Pfadalias angegeben werden, der auf ein existierendes Verzeichnis verweist. Wird nichts angegeben, wird das migrations-Unterverzeichnis im Basispfad verwendet.

  • migrationTable: string, gibt den Namen der Tabelle für die Migrationshistorie an. Vorgabewert ist tbl_migration. Die Tabellenstruktur ist version varchar(255) primary key, apply_time integer.

  • connectionID: string, die ID der DB-Komponenten. Standardmäßig 'db'.

  • templateFile: string, gibt den Pfadalias zu einer Vorlagendatei für neue Migrationsklassen an (z.B. application.migrations.template). Wird diese Option nicht angegeben, wird eine interne Vorlage verwendet. Innerhalb des Templates wird der Token {ClassName} später mit dem eigentlichen Klassennamen ersetzt.

Diese Optionen können wie folgt angegeben werden:

yiic migrate up --option1=value1 --option2=value2 ...

Will man zum Beispiel ein Modul forum migrieren, dessen Migrationsdateien im migrations-Verzeichnis des Moduls zu finden sind, eignet sich dieser Befehl:

yiic migrate up --migrationPath=ext.forum.migrations

Über globale Konfiguration des Befehls

Während man mit den Kommandozeilenoptionen die Migration direkt beeinflussen kann, ist es manchmal einfacher, die Konfiguration einmalig für alle Aufrufe zu verändern. Evtl. möchte man ja eine andere Tabelle für die Migrationshistorie oder eine angepasste Vorlage verwenden. Dazu kann man die Konfiguration für Konsolenanwendungen folgendermaßen anpassen:

return array(
    ......
    'commandMap'=>array(
        'migrate'=>array(
            'class'=>'system.cli.commands.MigrateCommand',
            'migrationPath'=>'application.migrations',
            'migrationTable'=>'tbl_migration',
            'connectionID'=>'db',
            'templateFile'=>'application.migrations.template',
        ),
        ......
    ),
    ......
);

Ruft man nun den migrate-Befehl auf, werden obige Optionen immer angewendet, ohne sie explizit mit angeben zu müssen.

Be the first person to leave a comment

Please to leave your comment.