Migration-Scripte mit Doctrine erstellen: Einführung

Eines meiner Lieblingsthemen ist das „Database refactoring“. Dabei geht es um Änderungen eines Datenbankschemas über die Zeit. Interessant wird dieses Thema, wenn bereits Datensätze in den Tabellen sind: Diese sollen natürlich nicht verloren gehen. Ändert sich bspw. das Datenformat einer Spalte, müssen die Daten auch entsprechend angepasst werden. Dies wird anhand von Migrations-Skripten vorgenommen. In dieser kleinen Artikelserie möchte ich die Funktionsweise von Doctrine Migrations genauer erklären. Diese Bibliothek unterstützt den Programmierer unter PHP bei der Erstellung dieser Datenbank-Migrationskripte.

Allgemeines

Wie man Doctrine2 in das ZendFramework2 einbindet hatte ich bereits vor einiger Zeit beschrieben. Doctrine an sich ist die Zwischenschicht zwischen Datenbank und Geschäftslogik. Der Fachbegriff ist auch Object Relational Mapping (ORM) und ist ein Konzept, um Objekte in relationalen Datenbanken abzubilden. Ein Objekt bildet dabei eine Zeile in einer Datenbank ab. Mit Hilfe von Doctrine können Datenbank-Schemen, Entities und Repositories angelegt werden. Diese werden so neutral erstellt, dass diese (theoretisch) für viele Datenbankmanagementsysteme genutzt werden können. Was dem Programmierers Freud, ist des Datenbankadmins Leid: Die Tabellen können meistens nicht mit den idealen Datentypen ausgestattet werden, weil das ORM diese nicht unterstützt. Wer ein ORM nutzt muss folglich einige Einschränkungen in Kauf nehmen.

Worum geht es nun in meiner Artikelserie? Gehen wir davon aus, wir haben ein PHP-Projekt mit mehreren Modulen. Jedes Modul nutzt Doctrine2, das heißt, es existieren in jedem Modul Konfigurationsdateien und Entities, die das entsprechende Datenbankschema abbilden. Das Problem ist nun beim zusammenstellen der eigenen Anwendung, das Datenbankschema aktuell zu halten. Bei jedem Release kann sich das Schema der Entities in den einzelnen Modulen geändert haben. Wir müssen nun diese Änderungen in unserer Datenbank ebenfalls durchführen und zusätzlich die schon vorhandenen Daten erhalten. Aktualisieren wir das Schema direkt über Doctrine, könnten die Daten einfach gelöscht werden. Um dieses Problem zu umgehen wurde für Doctrine eine Erweiterung Namens Doctrine Migrations erstellt. Diese kümmert sich um die Erstellung von Migrations-Scripten und das Management der einzelnen Versionen. Dazu später mehr.

Was wird nun eigentlich alles gebraucht? Zunächst eine Tabelle, in der die Änderungen erfasst werden. In dieser steht drin, auf welchem Datenbankstand wir gerade sind. Wie die Versionierung erfolgt ist jedem Projekt selbst überlassen. Bei Doctrine wird das aktuelle Datum und die Zeit genutzt: YYYYMMDDHHmmss. Damit wären wir auch schon beim zweiten wichtigen Bestandteil: Die Migrations-Scripte. Wie sieht so ein Script aus? Sehen wir uns dazu eine von Doctrine Migration erzeugte PHP-Datei an:

<?php

namespace Application\Migrations; 

use Doctrine\DBAL\Migrations\AbstractMigration; 
use Doctrine\DBAL\Schema\Schema; 

/** 
 * Auto-generated Migration: Please modify to your needs! 
 */ 
class Version20130823200401 extends AbstractMigration 
{ 
    public function up(Schema $schema) 
    { 
        // this up() migration is auto-generated, please modify it to your needs 
        $this->abortIf($this->connection->getDatabasePlatform()->getName() != "mysql", "Migration can only be executed safely on 'mysql'.");
        
        $this->addSql("CREATE TABLE Post (id INT AUTO_INCREMENT NOT NULL, headline VARCHAR(255) NOT NULL, content LONGTEXT NOT NULL, PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8 COLLATE utf8_unicode_ci ENGINE = InnoDB");
    }

    public function down(Schema $schema)
    {
        // this down() migration is auto-generated, please modify it to your needs
        $this->abortIf($this->connection->getDatabasePlatform()->getName() != "mysql", "Migration can only be executed safely on 'mysql'.");
        
        $this->addSql("DROP TABLE Post");
    }
}

Zunächst fällt auf, das es zwei Methoden gibt: up und down. Alles was in der Methode up steht wird ausgeführt, wenn die Migration nach oben ausgeführt wird, das heißt in der Versions-Tabelle steht, das diese Migration noch nicht durchgeführt wurde. In der down-Methode steht hingegen, was mit den Tabellen und den Daten passiert, wenn diese Migration rückgängig gemacht werden soll. Das ist wichtig wenn man seine Software auf einen früheren Stand zurücksetzen muss.

Und damit sind wir schon bei dem Problem der Daten. Wenn eine Änderung in der Tabelle dazu führt, das Daten mit Verlust geändert werden müssen, können diese natürlich schwer wiederhergestellt werden. Vor einer Änderung sollte man folglich immer ein Datenbackup machen. Aber im Idealfall funktioniert alles wie in der Migrations-Datei oben beschrieben: Ist die Migration noch nicht ausgeführt worden, wird die Tabelle angelegt. Wollen wir die Migration zurücknehmen, wird die Tabelle wieder gelöscht.

Wie man so eine Migration organisiert werde ich in einem späteren Artikel erklären. In den zwei Folgenden gehe ich zum praktischen Teil über und erkläre das Erstellen von Migrationen anhand vom zwei großen PHP-Frameworks: ZendFramework2 und Symfony2. Den Anfang dazu macht Symfony2.