Datenbankmigration
==================

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

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

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:

~~~
[php]
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.

~~~
[php]
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|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.

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:

~~~
[php]
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:

~~~
[php]
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.

>Note|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](http://dev.mysql.com/doc/refman/5.1/en/implicit-commit.html)
>verursachen.

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


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.

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.

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.


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

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:

~~~
[php]
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.

